diff --git a/.docforge/documentation/documentation.yaml b/.docforge/documentation/documentation.yaml index f634d34e5..454b23027 100644 --- a/.docforge/documentation/documentation.yaml +++ b/.docforge/documentation/documentation.yaml @@ -1,18 +1,11 @@ structure: - file: _index.md - frontmatter: - title: Docs + source: /website/documentation/_index.md - dir: getting-started structure: - fileTree: /website/documentation/getting-started - dir: guides structure: - - file: _index.md - frontmatter: - title: Guides - description: Walkthroughs of common activities - persona: Developers - weight: 20 - manifest: ./guides.yaml - dir: security-and-compliance structure: @@ -21,7 +14,7 @@ structure: frontmatter: title: Security and Compliance description: Make sure that your clusters are compliant and secure - weight: 30 + weight: 36 aliases: ["/docs/security-and-compliance/"] source: https://github.com/gardener/.github/blob/main/SECURITY.md - file: https://github.com/gardener/diki/blob/main/docs/usage/disa-k8s-stig-shoot.md @@ -47,7 +40,7 @@ structure: frontmatter: title: API Reference weight: 10 - aliases: + aliases: - "/api-reference/" persona: Developers source: https://github.com/gardener/gardener/blob/master/docs/api-reference/README.md @@ -63,7 +56,6 @@ structure: persona: Operators - fileTree: https://github.com/gardener/gardener/tree/master/docs/concepts - file: https://github.com/gardener/gardener/blob/master/logo/gardenadm-large.png - - file: https://github.com/gardener/gardener/blob/master/docs/proposals/assets/28-overview.png - dir: extensions structure: - file: _index.md @@ -176,6 +168,17 @@ structure: - "high-availability/shoot_high_availability_best_practices.md" # already included in ./guides.yaml, avoiding duplicates - "high-availability/shoot_high_availability.md" # already included in ./guides.yaml, avoiding duplicates - "autoscaling/shoot_pod_autoscaling_best_practices.md" # already included in ./guides.yaml, avoiding duplicates + - dir: proposals + structure: + - file: _index.md + frontmatter: + title: Proposals + weight: 34 + source: https://github.com/gardener/enhancements/blob/main/geps/README.md + - fileTree: https://github.com/gardener/enhancements/tree/main/geps + excludeFiles: + - README.md + - NNNN-gep-template/README.md - dir: extensions structure: - file: _index.md @@ -187,11 +190,6 @@ structure: - manifest: ./gardener-extensions/gardener-extensions.yaml - dir: other-components structure: - - file: _index.md - frontmatter: - title: Other Components - description: Other components included in the Gardener project - weight: 50 - manifest: ./other-components.yaml - dir: dashboard structure: @@ -217,9 +215,8 @@ structure: - dir: faq structure: - manifest: ./faq.yaml - - dir: glossary - structure: - - fileTree: /website/documentation/glossary + - file: glossary.md + source: /website/documentation/glossary.md - dir: resources structure: - fileTree: /website/documentation/resources diff --git a/.docforge/documentation/other-components.yaml b/.docforge/documentation/other-components.yaml index aaa1efdcb..bd026a23e 100644 --- a/.docforge/documentation/other-components.yaml +++ b/.docforge/documentation/other-components.yaml @@ -1,4 +1,6 @@ structure: +- file: _index.md + source: /website/documentation/other-components/_index.md - dir: machine-controller-manager structure: - file: _index.md diff --git a/.dockerignore b/.dockerignore index bbcd798dc..ccf865b14 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,5 +6,6 @@ !images/ !post-processing/ !website/ +!Makefile !package*.json !tsconfig.json diff --git a/.github/workflows/deploy-github-pages.yaml b/.github/workflows/deploy-github-pages.yaml index ab69a5191..9b9f4e3ac 100644 --- a/.github/workflows/deploy-github-pages.yaml +++ b/.github/workflows/deploy-github-pages.yaml @@ -39,33 +39,24 @@ jobs: with: path: .vitepress/dist if: github.event_name != 'pull_request' - - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node - uses: actions/setup-node@v4 + - name: Send failure notification + uses: gardener/cc-utils/.github/actions/send-mail@master + if: ${{ failure() && github.ref == 'refs/heads/master' }} with: - node-version: 24 - cache: npm - - name: Setup Pages - uses: actions/configure-pages@v5 - - name: Test - run: make ci-test - env: - GITHUB_OAUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + subject: GHA workflow ${{ github.workflow_ref }} failed! + body: | + The following GHA job failed: + ${build_job_url} + recipients: | + niklas.klocke@sap.com + marc.vornetran@sap.com deploy: if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} - needs: [build, test] + needs: [build] runs-on: ubuntu-latest name: Deploy steps: diff --git a/.github/workflows/pr-preview.yaml b/.github/workflows/pr-preview.yaml index 3d28ec20c..61783a5c2 100644 --- a/.github/workflows/pr-preview.yaml +++ b/.github/workflows/pr-preview.yaml @@ -6,6 +6,7 @@ on: - closed permissions: + id-token: write pull-requests: write concurrency: preview-${{ github.ref }} @@ -15,14 +16,13 @@ jobs: if: ${{ github.event.action == 'labeled' && github.event.label.name == vars.DEFAULT_LABEL_OK_TO_TEST && vars.DEFAULT_LABEL_OK_TO_TEST != '' }} runs-on: ubuntu-latest steps: - - uses: actions/create-github-app-token@v2 + - uses: gardener/cc-utils/.github/actions/github-auth@master id: app-token with: - # required - app-id: ${{ vars.GARDENER_GITHUB_ACTIONS_APP_ID }} - private-key: ${{ secrets.GARDENER_GITHUB_ACTIONS_PRIVATE_KEY }} - owner: gardener + token-server: ${{ vars.FEDERATED_GITHUB_ACCESS_TOKEN_SERVER }} repositories: documentation-demo + permissions: | + contents: write - name: Checkout uses: gardener/cc-utils/.github/actions/trusted-checkout@master @@ -51,7 +51,7 @@ jobs: - name: Build with VitePress run: | echo "Building with VITE_PUBLIC_BASE_PATH: '$VITE_PUBLIC_BASE_PATH'" - npm run docs:build + make build - uses: actions/checkout@v6 with: @@ -193,14 +193,13 @@ jobs: if: ${{ github.event.action == 'closed' }} runs-on: ubuntu-latest steps: - - uses: actions/create-github-app-token@v2 + - uses: gardener/cc-utils/.github/actions/github-auth@master id: app-token with: - # required - app-id: ${{ vars.GARDENER_GITHUB_ACTIONS_APP_ID }} - private-key: ${{ secrets.GARDENER_GITHUB_ACTIONS_PRIVATE_KEY }} - owner: gardener + token-server: ${{ vars.FEDERATED_GITHUB_ACCESS_TOKEN_SERVER }} repositories: documentation-demo + permissions: | + contents: write - name: Checkout main repository uses: gardener/cc-utils/.github/actions/trusted-checkout@master diff --git a/.github/workflows/pullrequest-trust-helper.yaml b/.github/workflows/pullrequest-trust-helper.yaml index a14d18b9b..b939780b6 100644 --- a/.github/workflows/pullrequest-trust-helper.yaml +++ b/.github/workflows/pullrequest-trust-helper.yaml @@ -9,8 +9,7 @@ on: jobs: pullrequest-trusted-helper: permissions: - pull-requests: write - secrets: inherit # access to `GitHub-Actions`-App is needed to read teams + id-token: write uses: gardener/cc-utils/.github/workflows/pullrequest-trust-helper.yaml@master with: - trusted-teams: 'documentation-maintainers' \ No newline at end of file + trusted-teams: 'documentation-maintainers' diff --git a/.vitepress/config.mts b/.vitepress/config.mts index aac7634bf..48ae208d6 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -1,15 +1,134 @@ import { defineConfig } from 'vitepress' -import { fileURLToPath, URL } from 'node:url' +import { fileURLToPath, URL, pathToFileURL } from 'node:url' import blogSidebar from './theme/blog-sidebar.ts' import {communitySidebar} from "./theme/community-sidebar.ts"; import path from 'path' +import fs from 'fs' +import { SearchResult } from 'minisearch' +import { generateEnhancedDocsSidebar } from './theme/docs-sidebar.ts'; const indexPattern = new RegExp(/\/?_?index\.md$/i); +// Pre-compute sidebar data at module level (runs once at build start) +const allSidebars = { + '/blog/': blogSidebar()['/blog/'], + '/community/': communitySidebar()['/community/'], + '/docs/': generateEnhancedDocsSidebar()['/docs/'] +}; + +/** + * Check if a markdown file has meaningful body content (beyond frontmatter). + */ +function hasMarkdownContent(filePath: string): boolean { + try { + const raw = fs.readFileSync(filePath, 'utf-8') + // Strip frontmatter (--- ... ---) + const stripped = raw.replace(/^---[\s\S]*?---\s*/, '') + // Check if remaining content has meaningful text + return stripped.trim().length > 0 + } catch { + return true // If we can't read, assume it has content + } +} + +interface TaxonomySidebarItem { + text?: string + link?: string + items?: TaxonomySidebarItem[] + collapsed?: boolean +} + +/** + * Normalize a path for comparison. + * e.g. 'gardener/advanced/' -> 'gardener/advanced/index.md' + */ +function normalizeSidebarPath(base: string, link: string): string { + let p = base + link + if (p.endsWith('/')) p += 'index.md' + else if (!p.endsWith('.md')) p += '/index.md' + // Remove leading slash for comparison with relativePath + return p.replace(/^\//, '') +} + +/** + * Recursively find sidebar children for a given page path. + */ +function findSidebarChildren( + items: TaxonomySidebarItem[], + base: string, + targetRelativePath: string +): Array<{ text: string; link: string }> | null { + for (const item of items) { + if (item.link) { + const normalized = normalizeSidebarPath(base, item.link) + if (normalized === targetRelativePath && item.items && item.items.length > 0) { + return item.items + .filter(child => child.text && child.link) + .map(child => ({ + text: child.text!, + link: base + child.link!, + })) + } + } + if (item.items) { + const found = findSidebarChildren(item.items, base, targetRelativePath) + if (found) return found + } + } + return null +} + +/** + * Find taxonomy children for a page across all sidebar configs. + */ +function getTaxonomyChildren(relativePath: string): Array<{ text: string; link: string }> | null { + for (const [, section] of Object.entries(allSidebars)) { + const sidebarSection = section as { base?: string; items?: TaxonomySidebarItem[] } + if (!sidebarSection.items) continue + const base = sidebarSection.base || '' + + // Check if this page IS the section root + const sectionRootNormalized = normalizeSidebarPath(base, '').replace(/^\//, '') + if (sectionRootNormalized === relativePath && sidebarSection.items.length > 0) { + return sidebarSection.items + .filter(item => item.text && item.link) + .map(item => ({ + text: item.text!, + link: base + item.link!, + })) + } + + // Search deeper in the tree + const found = findSidebarChildren(sidebarSection.items, base, relativePath) + if (found) return found + } + return null +} + export default defineConfig({ base: process.env.VITE_PUBLIC_BASE_PATH || '', srcDir: 'hugo/content', cleanUrls: true, + transformPageData(pageData) { + // Only process index pages + if (!pageData.relativePath.endsWith('index.md')) return + + // Build the absolute file path + const configDir = path.dirname(fileURLToPath(import.meta.url)) + const filePath = path.resolve(configDir, '..', 'hugo', 'content', pageData.relativePath) + + // Check if the markdown body is empty + if (hasMarkdownContent(filePath)) return + + // Look up sidebar children for this path + const children = getTaxonomyChildren(pageData.relativePath) + if (children && children.length > 0) { + pageData.frontmatter.taxonomyChildren = children + } + }, + sitemap: { + hostname: 'https://gardener.cloud' + }, rewrites(id) { if (!indexPattern.test(id) && id.endsWith('.md')) { return id.slice(0, -3) + '/index.md'; @@ -66,31 +185,18 @@ export default defineConfig({ ) function getNavConfig () { return [ - { - text: 'Demo', - link: 'https://demo.gardener.cloud/', - }, { text: 'Adopters', link: '/adopter', activeMatch: 'adopter', }, { - component: 'VPNavbarMenuGroupWrapper', - props: { - text: 'Documentation', - link: '/docs/', - activeMatch: 'docs', - items: [ - {text: 'User', link: '/docs/index.md',}, - {text: 'Operator', link: '/docs/index.md',}, - {text: 'Developer', link: '/docs/index.md',}, - {text: 'All', link: '/docs/index.md',}, - ], - }, + text: 'Documentation', + activeMatch: 'docs', + link: '/docs', }, { - text: 'Blogs', + text: 'Blog', link: '/blog', activeMatch: 'blog', }, @@ -159,7 +265,14 @@ function getSearchConfig() { page_synonyms: 3 // Synonyms/alternate terms }, // Fields to search in - fields: ['title', 'text', 'headings', 'tags', 'categories', 'description', 'page_synonyms'] + fields: ['title', 'text', 'headings', 'tags', 'categories', 'description', 'page_synonyms'], + // TODO(marc1404): Remove once `_index.md` files are renamed to `index.md`. + // Historically our source documentation files are using `_index.md` as the default name for index pages. + // Since migrating from Hugo to VitePress, we're using a post-processing step (post-processing/part-index.js) to copy and rename all `_index.md` files to `index.md`. + // This leads to duplicate search results since both `_index.md` and `index.md` are indexed by MiniSearch. + filter(result: SearchResult) { + return !result.id.includes('/_index'); + }, } } } @@ -171,30 +284,9 @@ function getThemeConfig() { isNetlify: process.env.NETLIFY === 'true', logo: {src: '/gardener-logo.svg', width: 24, height: 24}, nav: getNavConfig(), - sidebar: { - '/blog/': blogSidebar()['/blog/'], - //@ts-ignore - '/community/': communitySidebar()['/community/'], - //@ts-ignore - '/docs/': { //generateEnhancedDocsSidebar()['/docs/'], - "base": "/docs/", - "text": "Docs", - "items": [ - { - "text": "Gardener", - "link": "gardener/index.md", - "items": [ - { - "text": "Concepts", - "link": "gardener/concepts/index.md", - } - ] - } - ], - }, - }, + sidebar: allSidebars, editLink: { - pattern: ({filePath, frontmatter}) => { + pattern: ({filePath, frontmatter}: {filePath: string, frontmatter: Record}) => { const fileName = `${frontmatter?.path_base_for_github_subdir?.to ?? filePath.split("/").pop()}` const githubLink = `${frontmatter['github_repo']}/tree/master/${frontmatter['github_subdir']}/${fileName}` return githubLink @@ -240,11 +332,11 @@ function getViteConfig() { alias: [ { find: '@data', - replacement: path.resolve(__dirname, './data') + replacement: path.resolve(path.dirname(fileURLToPath(import.meta.url)), './data') }, { find: '@components', - replacement: path.resolve(__dirname, './theme/components') + replacement: path.resolve(path.dirname(fileURLToPath(import.meta.url)), './theme/components') }, { find: /^.*\/VPFeature\.vue$/, @@ -258,24 +350,6 @@ function getViteConfig() { new URL('./theme/components/VPTeamMembersItem.vue', import.meta.url) ) }, - { - find: /^.*\/VPNavBarMenuLink\.vue$/, - replacement: fileURLToPath( - new URL('./theme/components/VPNavBarMenuLink.vue', import.meta.url) - ) - }, - { - find: /^.*\/VPSidebarGroup\.vue$/, - replacement: fileURLToPath( - new URL('./theme/components/VPSidebarGroup.vue', import.meta.url) - ) - }, - { - find: /^.*\/VPMenu\.vue$/, - replacement: fileURLToPath( - new URL('./theme/components/VPMenu.vue', import.meta.url) - ) - }, ] } } diff --git a/.vitepress/data/sidebar.data.ts b/.vitepress/data/sidebar.data.ts deleted file mode 100644 index 999f2ef35..000000000 --- a/.vitepress/data/sidebar.data.ts +++ /dev/null @@ -1,14 +0,0 @@ - -import { personaSidebar, generateEnhancedDocsSidebar } from '../theme/docs-sidebar.ts' - -//https://vitepress.dev/guide/data-loading#build-time-data-loading -export default{ - load() { - return { - usersSidebar: personaSidebar('Users')['/docs/'], - developersSidebar: personaSidebar('Developers')['/docs/'], - operatorsSidebar: personaSidebar('Operators')['/docs/'], - all : generateEnhancedDocsSidebar()['/docs/'] - } - } -} \ No newline at end of file diff --git a/.vitepress/theme/community-sidebar.ts b/.vitepress/theme/community-sidebar.ts index a98649476..539548851 100644 --- a/.vitepress/theme/community-sidebar.ts +++ b/.vitepress/theme/community-sidebar.ts @@ -1,5 +1,4 @@ import { generateSidebar } from 'vitepress-sidebar'; -import { writeJsonDebug } from './utils/debug-json.ts'; import { removeIndexEntries, sortByWeight, @@ -33,18 +32,8 @@ export function communitySidebar(): any { // Filter out all _index.md entries (called last) const filteredSidebar = removeIndexEntries(sortedSidebar) - writeJsonDebug( - 'filteredCommunitySidebar.json', - filteredSidebar - ); - const cleandSidebar = removeEmptyItems(filteredSidebar) - writeJsonDebug( - 'cleandCommunitySidebar.json', - cleandSidebar - ); - addTrailingSlashToLinks(cleandSidebar['/community/'].items); return cleandSidebar; diff --git a/.vitepress/theme/components/CustomVPMenuLink.vue b/.vitepress/theme/components/CustomVPMenuLink.vue deleted file mode 100644 index c7b6dc77b..000000000 --- a/.vitepress/theme/components/CustomVPMenuLink.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.vitepress/theme/components/VPMenu.vue b/.vitepress/theme/components/VPMenu.vue deleted file mode 100644 index d549b0407..000000000 --- a/.vitepress/theme/components/VPMenu.vue +++ /dev/null @@ -1,105 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.vitepress/theme/components/VPNavBarMenuLink.vue b/.vitepress/theme/components/VPNavBarMenuLink.vue deleted file mode 100644 index fc6691759..000000000 --- a/.vitepress/theme/components/VPNavBarMenuLink.vue +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.vitepress/theme/components/VPNavbarMenuGroupWrapper.vue b/.vitepress/theme/components/VPNavbarMenuGroupWrapper.vue deleted file mode 100644 index a74c13af3..000000000 --- a/.vitepress/theme/components/VPNavbarMenuGroupWrapper.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/.vitepress/theme/components/VPSidebarGroup.vue b/.vitepress/theme/components/VPSidebarGroup.vue deleted file mode 100644 index c70f62270..000000000 --- a/.vitepress/theme/components/VPSidebarGroup.vue +++ /dev/null @@ -1,238 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.vitepress/theme/docs-sidebar.ts b/.vitepress/theme/docs-sidebar.ts index 1e008c364..cdc2ed4fc 100644 --- a/.vitepress/theme/docs-sidebar.ts +++ b/.vitepress/theme/docs-sidebar.ts @@ -1,14 +1,8 @@ import { generateSidebar } from 'vitepress-sidebar'; -import { writeJsonDebug } from './utils/debug-json.ts'; import { - type SidebarItem, removeIndexEntries, sortByWeight, enhanceDirectoryTitles, - createLeafMap, - extractItems, - filterLeafMapByPersona, - filterSidebarByLeafMap, removeEmptyItems, addTrailingSlashToLinks } from './utils/sidebar.ts'; @@ -42,76 +36,12 @@ export function generateEnhancedDocsSidebar(): any { // Sort entries by weight from frontmatter const sortedSidebar = sortByWeight(enhancedSidebar, 'docs'); - // Log the sorted sidebar for debugging - writeJsonDebug('sortedSidebar.json', sortedSidebar); - // Filter out all _index.md entries (called last) const filteredSidebar = removeIndexEntries(sortedSidebar); - writeJsonDebug( - 'filteredSidebar.json', - filteredSidebar - ); - const cleandSidebar = removeEmptyItems(filteredSidebar) - writeJsonDebug( - 'cleandSidebar.json', - cleandSidebar - ); - addTrailingSlashToLinks(cleandSidebar['/docs/'].items); return cleandSidebar; } - -export function personaSidebar(persona: 'Users' | 'Developers' | 'Operators') { - // Use the enhanced sidebar instead of the basic one - const generatedSidebar = generateEnhancedDocsSidebar(); - - // Get all items from all sections of the sidebar - const allItems: SidebarItem[] = Object.values(generatedSidebar) - .flatMap(section => extractItems(section)); - - // Create the leaf map - const leafMap = createLeafMap(allItems); - - // Write both the generated sidebar and the leaf map to files - writeJsonDebug( - 'generatedSidebar.json', - generatedSidebar - ); - - writeJsonDebug( - `${persona}leafMap.json`, - Object.fromEntries(leafMap) - ); - - - // Apply filtering for persona and write to file - const personaLeafMap = filterLeafMapByPersona(leafMap, persona); - writeJsonDebug( - `/${persona}LeafMap.json`, - Object.fromEntries(personaLeafMap) - ); - - // Create sidebar by filtering the generated sidebar - const { filtered: sidebar, deleted: deletedItems } = filterSidebarByLeafMap( - generatedSidebar, - personaLeafMap - ); - - // Write the filtered sidebar and debug info to files - writeJsonDebug( - `/${persona}Sidebar.json`, - sidebar - ); - - writeJsonDebug( - 'deletedItems.json', - deletedItems - ); - - - return sidebar -} diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts index 78bd9201f..9a5fa8ef3 100644 --- a/.vitepress/theme/index.ts +++ b/.vitepress/theme/index.ts @@ -3,7 +3,6 @@ import { Theme, useData } from 'vitepress' import DefaultTheme from 'vitepress/theme' import YouTubeVideo from './components/YouTubeVideo.vue' import VPFooter from './components/VPFooter.vue' -import VPNavbarMenuGroupWrapper from './components/VPNavbarMenuGroupWrapper.vue' import EmptyIndexLayout from './layouts/EmptyIndexLayout.vue' import './style.css' @@ -20,8 +19,6 @@ export default { enhanceApp({ app, router, siteData }) { app.component('YouTubeVideo', YouTubeVideo) app.component('VPFooter', VPFooter) - app.component('VPNavbarMenuGroupWrapper', VPNavbarMenuGroupWrapper) - // Handle 404 detection for both initial loads and SPA navigation if (typeof window !== 'undefined') { let lastTrackedPath = ''; diff --git a/.vitepress/theme/layouts/EmptyIndexLayout.vue b/.vitepress/theme/layouts/EmptyIndexLayout.vue index c1e728cea..a1a196b2a 100644 --- a/.vitepress/theme/layouts/EmptyIndexLayout.vue +++ b/.vitepress/theme/layouts/EmptyIndexLayout.vue @@ -1,266 +1,63 @@ + + - + \ No newline at end of file diff --git a/.vitepress/theme/style.css b/.vitepress/theme/style.css index 9ebe3298f..be6c8d0eb 100644 --- a/.vitepress/theme/style.css +++ b/.vitepress/theme/style.css @@ -71,3 +71,23 @@ img[src='/search.png'] { .VPSocialLink[href*="github"] { color: var(--vp-c-text-1); } + +/* Podrick button styling */ +.VPButton.alt[href*="podrick"]::after { + content: ''; + display: inline-block; + width: 35px; + height: 35px; + margin-left: 8px; + margin-right: -4px; + background-image: url('/podrick.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + vertical-align: middle; +} + +.VPButton.alt[href*="podrick"] { + display: inline-flex; + align-items: center; +} diff --git a/.vitepress/theme/utils/debug-json.ts b/.vitepress/theme/utils/debug-json.ts deleted file mode 100644 index ccf8071a8..000000000 --- a/.vitepress/theme/utils/debug-json.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { dirname } from 'path'; -import fs from 'node:fs'; - - -// Before writing the file, ensure the directory exists -export function writeJsonDebug(fileName: string, data: object): void { - const filePath = `${import.meta.dirname}/debug/${fileName}` - - const dir = dirname(filePath); - - // Create directory if it doesn't exist (recursive: true creates all needed directories) - fs.mkdirSync(dir, { recursive: true }); - - // Then write the file - fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); -} \ No newline at end of file diff --git a/.vitepress/theme/utils/personaMapping.json b/.vitepress/theme/utils/personaMapping.json deleted file mode 100644 index 61d31eb61..000000000 --- a/.vitepress/theme/utils/personaMapping.json +++ /dev/null @@ -1,265 +0,0 @@ -{ - "/": ["Developers", "Operators", "Users"], - "/docs/": ["Developers", "Operators", "Users"], - "/docs/dashboard/": ["Operators", "Users", "Developers"], - "/docs/dashboard/access-restrictions/": ["Operators"], - "/docs/dashboard/architecture/": ["Developers"], - "/docs/dashboard/automated-resource-management/": ["Users"], - "/docs/dashboard/connect-kubectl/": ["Users"], - "/docs/dashboard/custom-fields/": ["Users"], - "/docs/dashboard/customization/": ["Operators"], - "/docs/dashboard/local-setup/": ["Developers"], - "/docs/dashboard/process/": ["Developers"], - "/docs/dashboard/project-operations/": ["Users"], - "/docs/dashboard/terminal-shortcuts/": ["Users"], - "/docs/dashboard/testing/": ["Developers"], - "/docs/dashboard/using-terminal/": ["Users"], - "/docs/dashboard/webterminals/": ["Operators"], - "/docs/dashboard/working-with-projects/": ["Users"], - "/docs/extensions/": ["Operators", "Developers", "Users"], - "/docs/extensions/infrastructure-extensions/": ["Operators", "Developers", "Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-alicloud/": ["Operators", "Developers", "Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-alicloud/deployment/": ["Operators"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-alicloud/local-setup/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-alicloud/operations/": ["Operators"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-alicloud/tutorials/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-alicloud/usage/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/": ["Operators", "Developers", "Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/deployment/": ["Operators"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/dual-stack-ingress/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/images/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/ipv6/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/local-setup/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/operations/": ["Operators"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/tutorials/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/tutorials/kubernetes-cluster-on-aws-with-gardener/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/tutorials/kubernetes-cluster-on-aws-with-gardener/images/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/tutorials/kubernetes-cluster-on-aws-with-gardener/kubernetes-cluster-on-aws-with-gardener/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-aws/usage/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-azure/": ["Developers", "Operators", "Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-azure/azure-permissions/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-azure/deployment/": ["Operators"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-azure/images/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-azure/local-setup/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-azure/operations/": ["Operators"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-azure/tutorials/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-azure/usage/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-equinix-metal/": ["Operators", "Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-equinix-metal/operations/": ["Operators"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-equinix-metal/usage/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-gcp/": ["Developers", "Operators", "Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-gcp/deployment/": ["Operators"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-gcp/images/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-gcp/ipv6/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-gcp/local-setup/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-gcp/operations/": ["Operators"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-gcp/tutorials/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-gcp/usage/": ["Users"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-openstack/": ["Operators", "Users", "Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-openstack/deployment/": ["Operators"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-openstack/local-setup/": ["Developers"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-openstack/operations/": ["Operators"], - "/docs/extensions/infrastructure-extensions/gardener-extension-provider-openstack/usage/": ["Users"], - "/docs/extensions/network-extensions/": ["Operators", "Users"], - "/docs/extensions/network-extensions/gardener-extension-networking-calico/": ["Operators", "Users"], - "/docs/extensions/network-extensions/gardener-extension-networking-calico/assets/": ["Users"], - "/docs/extensions/network-extensions/gardener-extension-networking-calico/deployment/": ["Operators"], - "/docs/extensions/network-extensions/gardener-extension-networking-calico/operations/": ["Operators"], - "/docs/extensions/network-extensions/gardener-extension-networking-calico/shoot_overlay_network/": ["Users"], - "/docs/extensions/network-extensions/gardener-extension-networking-calico/usage/": ["Users"], - "/docs/extensions/network-extensions/gardener-extension-networking-cilium/": ["Users"], - "/docs/extensions/network-extensions/gardener-extension-networking-cilium/usage/": ["Users"], - "/docs/extensions/os-extensions/": ["Users"], - "/docs/extensions/os-extensions/gardener-extension-os-coreos/": ["Users"], - "/docs/extensions/os-extensions/gardener-extension-os-coreos/usage/": ["Users"], - "/docs/extensions/os-extensions/gardener-extension-os-suse-chost/": ["Users"], - "/docs/extensions/os-extensions/gardener-extension-os-suse-chost/usage/": ["Users"], - "/docs/extensions/os-extensions/gardener-extension-os-ubuntu/": ["Users"], - "/docs/extensions/os-extensions/gardener-extension-os-ubuntu/usage/": ["Users"], - "/docs/extensions/others/": ["Operators", "Users", "Developers"], - "/docs/extensions/others/gardener-extension-registry-cache/": ["Developers", "Users"], - "/docs/extensions/others/gardener-extension-registry-cache/extension-registry-cache/": ["Developers"], - "/docs/extensions/others/gardener-extension-registry-cache/getting-started-locally/": ["Developers"], - "/docs/extensions/others/gardener-extension-registry-cache/getting-started-remotely/": ["Developers"], - "/docs/extensions/others/gardener-extension-registry-cache/registry-cache/": ["Users"], - "/docs/extensions/others/gardener-extension-registry-cache/registry-cache/configuration/": ["Users"], - "/docs/extensions/others/gardener-extension-registry-cache/registry-cache/images/": ["Users"], - "/docs/extensions/others/gardener-extension-registry-cache/registry-cache/observability/": ["Users"], - "/docs/extensions/others/gardener-extension-registry-cache/registry-cache/upstream-credentials/": ["Users"], - "/docs/extensions/others/gardener-extension-registry-cache/registry-mirror/": ["Users"], - "/docs/extensions/others/gardener-extension-registry-cache/registry-mirror/configuration/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-cert-service/": ["Users", "Operators"], - "/docs/extensions/others/gardener-extension-shoot-cert-service/alerting/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-cert-service/custom_shoot_issuer/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-cert-service/deployment/": ["Operators"], - "/docs/extensions/others/gardener-extension-shoot-cert-service/request_cert/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-cert-service/request_default_domain_cert/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-dns-service/": ["Operators", "Users"], - "/docs/extensions/others/gardener-extension-shoot-dns-service/configuration/": ["Operators"], - "/docs/extensions/others/gardener-extension-shoot-dns-service/deployment/": ["Operators"], - "/docs/extensions/others/gardener-extension-shoot-dns-service/dns_names/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-dns-service/dns_providers/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-dns-service/tutorials/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-dns-service/tutorials/gateway-api-gateways/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-dns-service/tutorials/istio-gateways/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-lakom-service/": ["Operators", "Users"], - "/docs/extensions/others/gardener-extension-shoot-lakom-service/deployment/": ["Operators"], - "/docs/extensions/others/gardener-extension-shoot-lakom-service/lakom/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-lakom-service/shoot-extension/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-networking-filter/": ["Users", "Operators"], - "/docs/extensions/others/gardener-extension-shoot-networking-filter/deployment/": ["Operators"], - "/docs/extensions/others/gardener-extension-shoot-networking-filter/shoot-networking-filter/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-networking-problemdetector/": ["Operators", "Users"], - "/docs/extensions/others/gardener-extension-shoot-networking-problemdetector/deployment/": ["Operators"], - "/docs/extensions/others/gardener-extension-shoot-networking-problemdetector/shoot-networking-problemdetector/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-oidc-service/": ["Users", "Operators"], - "/docs/extensions/others/gardener-extension-shoot-oidc-service/deployment/": ["Operators"], - "/docs/extensions/others/gardener-extension-shoot-oidc-service/openidconnects/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-rsyslog-relp/": ["Users", "Developers"], - "/docs/extensions/others/gardener-extension-shoot-rsyslog-relp/configuration/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-rsyslog-relp/getting-started-remotely/": ["Developers"], - "/docs/extensions/others/gardener-extension-shoot-rsyslog-relp/getting-started/": ["Developers"], - "/docs/extensions/others/gardener-extension-shoot-rsyslog-relp/images/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-rsyslog-relp/monitoring/": ["Users"], - "/docs/extensions/others/gardener-extension-shoot-rsyslog-relp/shoot-rsyslog-relp/": ["Developers"], - "/docs/faq/": ["Developers"], - "/docs/gardener/": ["Developers", "Operators", "Users"], - "/docs/gardener/advanced/": ["Users"], - "/docs/gardener/advanced/containerd-registry-configuration/": ["Users"], - "/docs/gardener/advanced/control-plane-endpoints-and-ports/": ["Users"], - "/docs/gardener/advanced/csi_components/": ["Users"], - "/docs/gardener/advanced/custom-containerd-config/": ["Users"], - "/docs/gardener/advanced/images/": ["Users"], - "/docs/gardener/advanced/node-readiness/": ["Users"], - "/docs/gardener/advanced/shoot_cleanup/": ["Users"], - "/docs/gardener/advanced/tolerations/": ["Users"], - "/docs/gardener/api-reference/": ["Developers"], - "/docs/gardener/autoscaling-specifics-for-components/": ["Developers"], - "/docs/gardener/autoscaling/": ["Users"], - "/docs/gardener/autoscaling/dns-autoscaling/": ["Users"], - "/docs/gardener/autoscaling/shoot_autoscaling/": ["Users"], - "/docs/gardener/autoscaling/shoot_pod_autoscaling_best_practices/": ["Users"], - "/docs/gardener/changing-the-api/": ["Developers"], - "/docs/gardener/component-checklist/": ["Developers"], - "/docs/gardener/concepts/": ["Operators"], - "/docs/gardener/configuration/": ["Operators"], - "/docs/gardener/content/": ["Developers"], - "/docs/gardener/control_plane_migration/": ["Operators"], - "/docs/gardener/defaulting/": ["Developers"], - "/docs/gardener/dependencies/": ["Developers"], - "/docs/gardener/deployment/": ["Operators"], - "/docs/gardener/extensions/": ["Developers"], - "/docs/gardener/gardener/": ["Users"], - "/docs/gardener/gardener/gardener_info_configmap/": ["Users"], - "/docs/gardener/getting_started_locally/": ["Developers"], - "/docs/gardener/high-availability-of-components/": ["Developers"], - "/docs/gardener/high-availability/": ["Users"], - "/docs/gardener/high-availability/shoot_high_availability/": ["Users"], - "/docs/gardener/high-availability/shoot_high_availability_best_practices/": ["Users"], - "/docs/gardener/images/": ["Operators"], - "/docs/gardener/ipv6/": ["Developers"], - "/docs/gardener/istio/": ["Operators"], - "/docs/gardener/kube_apiserver_loadbalancing/": ["Operators"], - "/docs/gardener/kubernetes-clients/": ["Developers"], - "/docs/gardener/local_setup/": ["Developers"], - "/docs/gardener/log_parsers/": ["Developers"], - "/docs/gardener/logging/": ["Developers"], - "/docs/gardener/managed_seed/": ["Operators"], - "/docs/gardener/monitoring-stack/": ["Developers"], - "/docs/gardener/monitoring/": ["Operators"], - "/docs/gardener/network_policies/": ["Operators"], - "/docs/gardener/networking/": ["Users"], - "/docs/gardener/networking/custom-dns-config/": ["Users"], - "/docs/gardener/networking/dns-search-path-optimization/": ["Users"], - "/docs/gardener/networking/exposureclasses/": ["Users"], - "/docs/gardener/networking/images/": ["Users"], - "/docs/gardener/networking/node-local-dns/": ["Users"], - "/docs/gardener/networking/shoot_kubernetes_service_host_injection/": ["Users"], - "/docs/gardener/networking/shoot_networking/": ["Users"], - "/docs/gardener/new-cloud-provider/": ["Developers"], - "/docs/gardener/new-kubernetes-version/": ["Developers"], - "/docs/gardener/observability/": ["Users"], - "/docs/gardener/observability/images/": ["Users"], - "/docs/gardener/observability/logging/": ["Users"], - "/docs/gardener/priority-classes/": ["Developers"], - "/docs/gardener/process/": ["Developers"], - "/docs/gardener/project/": ["Users"], - "/docs/gardener/project/namespaced-cloud-profiles/": ["Users"], - "/docs/gardener/project/projects/": ["Users"], - "/docs/gardener/project/service-account-manager/": ["Users"], - "/docs/gardener/reversed-vpn-tunnel/": ["Developers"], - "/docs/gardener/secrets_management/": ["Developers"], - "/docs/gardener/security/": ["Users"], - "/docs/gardener/security/default_seccomp_profile/": ["Users"], - "/docs/gardener/security/etcd_encryption_config/": ["Users"], - "/docs/gardener/security/openidconnect-presets/": ["Users"], - "/docs/gardener/security/pod-security/": ["Users"], - "/docs/gardener/security/shoot_auditpolicy/": ["Users"], - "/docs/gardener/security/shoot_serviceaccounts/": ["Users"], - "/docs/gardener/seed_bootstrapping/": ["Operators"], - "/docs/gardener/seed_settings/": ["Operators"], - "/docs/gardener/shoot-operations/": ["Users"], - "/docs/gardener/shoot-operations/shoot_credentials_rotation/": ["Users"], - "/docs/gardener/shoot-operations/shoot_operations/": ["Users"], - "/docs/gardener/shoot-operations/shoot_updates/": ["Users"], - "/docs/gardener/shoot-operations/shoot_versions/": ["Users"], - "/docs/gardener/shoot-operations/supported_k8s_versions/": ["Users"], - "/docs/gardener/shoot-operations/worker_pool_k8s_versions/": ["Users"], - "/docs/gardener/shoot/": ["Users"], - "/docs/gardener/shoot/access_restrictions/": ["Users"], - "/docs/gardener/shoot/shoot_access/": ["Users"], - "/docs/gardener/shoot/shoot_hibernate/": ["Users"], - "/docs/gardener/shoot/shoot_info_configmap/": ["Users"], - "/docs/gardener/shoot/shoot_kubernetes_versions/": ["Users"], - "/docs/gardener/shoot/shoot_limits/": ["Users"], - "/docs/gardener/shoot/shoot_maintenance/": ["Users"], - "/docs/gardener/shoot/shoot_purposes/": ["Users"], - "/docs/gardener/shoot/shoot_scheduling_profiles/": ["Users"], - "/docs/gardener/shoot/shoot_status/": ["Users"], - "/docs/gardener/shoot/shoot_supported_architectures/": ["Users"], - "/docs/gardener/shoot/shoot_workerless/": ["Users"], - "/docs/gardener/shoot/shoot_workers_settings/": ["Users"], - "/docs/gardener/testing/": ["Developers"], - "/docs/gardener/testmachinery_tests/": ["Developers"], - "/docs/gardener/topology_aware_routing/": ["Operators"], - "/docs/gardener/trusted-tls-for-control-planes/": ["Operators"], - "/docs/gardener/trusted-tls-for-garden-runtime/": ["Operators"], - "/docs/guides/": ["Developers"], - "/docs/other-components/": ["Developers", "Operators", "Users"], - "/docs/other-components/dependency-watchdog/": ["Developers"], - "/docs/other-components/dependency-watchdog/contribution/": ["Developers"], - "/docs/other-components/dependency-watchdog/setup/": ["Developers"], - "/docs/other-components/dependency-watchdog/setup/dwd-using-local-garden/": ["Developers"], - "/docs/other-components/dependency-watchdog/testing/": ["Developers"], - "/docs/other-components/gardener-discovery-server/": ["Developers", "Operators"], - "/docs/other-components/gardener-discovery-server/api": ["Developers", "Operators"], - "/docs/other-components/etcd-druid/": ["Developers", "Users"], - "/docs/other-components/etcd-druid/add-new-etcd-cluster-component/": ["Developers"], - "/docs/other-components/etcd-druid/api-reference/": ["Developers"], - "/docs/other-components/etcd-druid/changing-api/": ["Developers"], - "/docs/other-components/etcd-druid/contribution/": ["Developers"], - "/docs/other-components/etcd-druid/controllers/": ["Developers"], - "/docs/other-components/etcd-druid/dependency-management/": ["Developers"], - "/docs/other-components/etcd-druid/getting-started-locally/": ["Developers"], - "/docs/other-components/etcd-druid/managing-etcd-clusters/": ["Users"], - "/docs/other-components/etcd-druid/prepare-dev-environment/": ["Developers"], - "/docs/other-components/etcd-druid/raising-a-pr/": ["Developers"], - "/docs/other-components/etcd-druid/recovering-etcd-clusters/": ["Users"], - "/docs/other-components/etcd-druid/running-e2e-tests/": ["Developers"], - "/docs/other-components/etcd-druid/securing-etcd-clusters/": ["Users"], - "/docs/other-components/etcd-druid/testing/": ["Developers"], - "/docs/other-components/etcd-druid/updating-documentation/": ["Developers"], - "/docs/other-components/etcd-druid/using-druid-client/": ["Users"], - "/docs/other-components/etcd-druid/validating-etcd-clusters/": ["Users"], - "/docs/other-components/machine-controller-manager/": ["Developers", "Operators"], - "/docs/other-components/machine-controller-manager/cp_support_new/": ["Developers"], - "/docs/other-components/machine-controller-manager/deployment/": ["Operators"], - "/docs/other-components/machine-controller-manager/integration_tests/": ["Developers"], - "/docs/other-components/machine-controller-manager/local_setup/": ["Developers"], - "/docs/other-components/machine-controller-manager/machine/": ["Operators"], - "/docs/other-components/machine-controller-manager/machine_deployment/": ["Operators"], - "/docs/other-components/machine-controller-manager/machine_error_codes/": ["Developers"], - "/docs/other-components/machine-controller-manager/machine_set/": ["Operators"], - "/docs/other-components/machine-controller-manager/prerequisite/": ["Operators"], - "/docs/other-components/machine-controller-manager/testing_and_dependencies/": ["Developers"] -} diff --git a/.vitepress/theme/utils/sidebar.ts b/.vitepress/theme/utils/sidebar.ts index 1506a66a5..1fab6b73e 100644 --- a/.vitepress/theme/utils/sidebar.ts +++ b/.vitepress/theme/utils/sidebar.ts @@ -282,26 +282,6 @@ export function getTitleFromIndexFile(link: string, base?: string): string | nul } } -/** - * Function to create a map of all leaf nodes - */ -export function createLeafMap(items: SidebarItem[]): Map { - const leafMap = new Map(); - - function processItem(item: SidebarItem) { - // If the item has a link and no items, it's a leaf node - if ('link' in item && !('items' in item)) { - leafMap.set(item.link, item); - } - // If it has items, process them recursively - if ('items' in item) { - item.items.forEach(processItem); - } - } - - items.forEach(processItem); - return leafMap; -} /** * Function to recursively extract all items from a section @@ -317,125 +297,6 @@ export function extractItems(section: any): SidebarItem[] { } -function removeTrailingSlash (str: string) { - return str.endsWith('/') ? str.slice(0, -1) : str; -} - -/** - * Function to filter leaf map based on persona permissions - * Only removes entries that are explicitly restricted for the persona. - * Entries not mentioned in the persona mapping are kept (available to all personas). - */ -export function filterLeafMapByPersona(leafMap: Map, persona: string): Map { - // Create a copy of the original map - const filteredMap = new Map(leafMap); - - // Read the persona mapping - const personaMapping = JSON.parse( - readFileSync(`${import.meta.dirname}/personaMapping.json`, 'utf-8') - ) as Record; - - // Process each entry in the persona mapping - for (const [path, allowedPersonas] of Object.entries(personaMapping)) { - // Only remove entries that are explicitly restricted (don't include this persona) - if (!allowedPersonas.includes(persona)) { - // Remove /docs/ prefix from the path - let strippedPath = path.replace('/docs/', ''); - strippedPath = removeTrailingSlash(strippedPath); - - // Find and remove all matching entries from filteredMap - for (const [leafKey] of filteredMap) { - if (leafKey.includes(strippedPath+ '/')) { - //delete dirs - filteredMap.delete(leafKey); - } - if (leafKey === strippedPath) { - //delete files - filteredMap.delete(leafKey); - } - } - } - } - - return filteredMap; -} - -/** - * Function to filter sidebar based on allowed leaf nodes - */ -export function filterSidebarByLeafMap( - sidebar: Record, - allowedLeafMap: Map -): { filtered: Record, deleted: SidebarLeaf[] } { - const deletedItems: SidebarLeaf[] = []; - - function filterItem(item: SidebarItem): SidebarItem | null { - // If it's a leaf node (has link but no items) - if ('link' in item && !('items' in item)) { - // If this leaf is not in the allowed map, add to deleted items - if (!allowedLeafMap.has(item.link)) { - deletedItems.push(item as SidebarLeaf); - return null; - } - - //Prefix the link with /docs/ - item.link = `/docs/${item.link}`; - return item; - } - - // If it's a branch node (has items) - if ('items' in item && Array.isArray(item.items)) { - const branch = item as SidebarBranch; - const filteredItems = branch.items - .map((subItem: SidebarItem) => filterItem(subItem)) - .filter((subItem): subItem is SidebarItem => subItem !== null); - - // If all items were filtered out, remove this branch too - if (filteredItems.length === 0) { - return null; - } - - return { - ...branch, - items: filteredItems - }; - } - - return item; - } - - // Create a deep copy of the sidebar - const filteredSidebar = JSON.parse(JSON.stringify(sidebar)); - - // Filter each section of the sidebar - for (const [path, section] of Object.entries(filteredSidebar)) { - if (Array.isArray(section)) { - // If the section is an array, filter its items - filteredSidebar[path] = section - .map(item => filterItem(item)) - .filter((item): item is SidebarItem => item !== null); - } else if (section && typeof section === 'object' && 'items' in section) { - // If the section has items, filter them - const sectionWithItems = section as { items: SidebarItem[] }; - const filteredItems = sectionWithItems.items - .map((item: SidebarItem) => filterItem(item)) - .filter((item): item is SidebarItem => item !== null); - - if (filteredItems.length === 0) { - // If all items were filtered out, remove the section - delete filteredSidebar[path]; - } else { - // Update the section with filtered items - filteredSidebar[path] = { - ...section, - items: filteredItems - }; - } - } - } - - return { filtered: filteredSidebar, deleted: deletedItems }; -} /** * Recursively removes all items fields from the sidebar object that are empty arrays diff --git a/Dockerfile b/Dockerfile index d2454382c..c4410d6bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,12 +10,12 @@ ADD . . RUN --mount=type=secret,id=GITHUB_OAUTH_TOKEN \ --mount=type=cache,target=/tmp/docforge \ - apk add --no-cache git && \ + apk add --no-cache git make && \ export GITHUB_OAUTH_TOKEN=$(cat /run/secrets/GITHUB_OAUTH_TOKEN) && \ export DOCFORGE_CONFIG='.docforge/config' && \ docforge --cache-dir /tmp/docforge && \ npm ci && \ - npm run post-process + make post-process EXPOSE 5173 diff --git a/Makefile b/Makefile index 1bd1511f2..13b48c5d9 100644 --- a/Makefile +++ b/Makefile @@ -110,8 +110,16 @@ dev: npx vitepress dev .PHONY: local-preview -local-preview: - docforge-ci install post-process npx vitepress preview +local-preview: ## Full local preview: clean hugo dir, run docforge, post-process, build, and preview + @if [ -d "hugo" ]; then \ + echo "Removing existing hugo/content directory..."; \ + rm -rf hugo; \ + fi + @$(MAKE) docforge-ci + @$(MAKE) install + @$(MAKE) post-process + @$(MAKE) build + npx vitepress preview .PHONY: post-processing-part-1 post-processing-part-1: @@ -127,7 +135,7 @@ post-processing-part-index: .PHONY: post-processing-part-3 post-processing-part-3: - node post-processing/part-3.js --add-empty-metadata --update-report-link --process-api-html + node post-processing/part-3.js --update-report-link --process-api-html .PHONY: post-process post-process: ## Run post-processing scripts @@ -140,14 +148,6 @@ post-process: ## Run post-processing scripts build: ## Build the documentation site VITE_PUBLIC_BASE_PATH='' npx vitepress build -.PHONY: test -test: ## Run tests - npx vitest run - -.PHONY: test-watch -test-watch: - npx vitest - .PHONY: docforge-ci docforge-ci: docforge-download ## Run docforge in CI mode (non-interactive) @echo "Running docforge (CI)..." @@ -155,7 +155,4 @@ docforge-ci: docforge-download ## Run docforge in CI mode (non-interactive) ./bin/docforge .PHONY: ci-build -ci-build: docforge-ci install post-process build ## Run all steps for building in CI - -.PHONY: ci-test -ci-test: docforge-ci install post-process test ## Run all steps for testing in CI +ci-build: docforge-ci install post-process build ## Run all steps for building in CI \ No newline at end of file diff --git a/README.md b/README.md index 6dabe628b..4dbef5b74 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ export GITHUB_OAUTH_TOKEN= 2. Build and run a Docker container: ```shell -make preview +make docker-preview ``` 3. Visit [http://localhost:5173](http://localhost:5173) in your browser! 🎉 diff --git a/package-lock.json b/package-lock.json index c8f93d119..9809f2f7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,26 +6,26 @@ "": { "name": "documentation", "dependencies": { - "@tailwindcss/typography": "^0.5.14", - "@types/js-yaml": "^4.0.9", - "@types/lodash-es": "^4.17.12", - "@types/markdown-it": "^12.2.3", - "@types/node": "^20.11.27", - "autoprefixer": "^10.4.0", - "feed": "^4.2.2", - "gray-matter": "^4.0.3", - "js-yaml": "^4.1.1", - "lodash-es": "^4.17.23", - "postcss": "^8.3.7", - "postcss-cli": "^9.0.2", - "tailwindcss": "^3.4.10", - "ts-node": "^10.9.2", - "tslib": "^2.8.1", - "vitepress": "^1.6.3", - "vitepress-plugin-autobar": "^1.0.8", - "vitepress-sidebar": "^1.31.1", - "vitest": "^3.2.4", - "vue": "^3.5.16" + "@tailwindcss/typography": "0.5.14", + "@types/js-yaml": "4.0.9", + "@types/lodash-es": "4.17.12", + "@types/markdown-it": "12.2.3", + "@types/node": "20.11.27", + "autoprefixer": "10.4.0", + "feed": "4.2.2", + "gray-matter": "4.0.3", + "js-yaml": "4.1.1", + "lodash-es": "4.17.23", + "postcss": "8.5.6", + "postcss-cli": "9.0.2", + "tailwindcss": "3.4.10", + "ts-node": "10.9.2", + "tslib": "2.8.1", + "vitepress": "1.6.3", + "vitepress-plugin-autobar": "1.0.8", + "vitepress-sidebar": "1.31.1", + "vitest": "3.2.4", + "vue": "3.5.16" } }, "node_modules/@algolia/autocomplete-core": { @@ -275,21 +275,21 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", - "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.7" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -299,13 +299,13 @@ } }, "node_modules/@babel/types": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", - "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1201,9 +1201,9 @@ "license": "MIT" }, "node_modules/@tailwindcss/typography": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", - "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.14.tgz", + "integrity": "sha512-ZvOCjUbsJBjL9CxQBn+VEnFpouzuKhxh2dH8xMIWHILL+HfOYtlAkWcyoon8LlzE53d2Yo6YO6pahKKNW3q1YQ==", "license": "MIT", "dependencies": { "lodash.castarray": "^4.4.0", @@ -1212,7 +1212,7 @@ "postcss-selector-parser": "6.0.10" }, "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + "tailwindcss": ">=3.0.0 || insiders" } }, "node_modules/@tsconfig/node10": { @@ -1322,12 +1322,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.2.tgz", - "integrity": "sha512-9pLGGwdzOUBDYi0GNjM97FIA+f92fqSke6joWeBjWXllfNxZBs7qeMF7tvtOIsbY45xkWkxrdwUfUf3MnQa9gA==", + "version": "20.11.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.27.tgz", + "integrity": "sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~5.26.4" } }, "node_modules/@types/unist": { @@ -1470,18 +1470,24 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz", - "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.16.tgz", + "integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.5", - "@vue/shared": "3.5.17", + "@babel/parser": "^7.27.2", + "@vue/shared": "3.5.16", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, + "node_modules/@vue/compiler-core/node_modules/@vue/shared": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", + "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "license": "MIT" + }, "node_modules/@vue/compiler-core/node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -1489,32 +1495,44 @@ "license": "MIT" }, "node_modules/@vue/compiler-dom": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", - "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz", + "integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-core": "3.5.16", + "@vue/shared": "3.5.16" } }, + "node_modules/@vue/compiler-dom/node_modules/@vue/shared": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", + "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "license": "MIT" + }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", - "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz", + "integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.5", - "@vue/compiler-core": "3.5.17", - "@vue/compiler-dom": "3.5.17", - "@vue/compiler-ssr": "3.5.17", - "@vue/shared": "3.5.17", + "@babel/parser": "^7.27.2", + "@vue/compiler-core": "3.5.16", + "@vue/compiler-dom": "3.5.16", + "@vue/compiler-ssr": "3.5.16", + "@vue/shared": "3.5.16", "estree-walker": "^2.0.2", "magic-string": "^0.30.17", - "postcss": "^8.5.6", + "postcss": "^8.5.3", "source-map-js": "^1.2.1" } }, + "node_modules/@vue/compiler-sfc/node_modules/@vue/shared": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", + "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "license": "MIT" + }, "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -1522,15 +1540,21 @@ "license": "MIT" }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", - "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz", + "integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-dom": "3.5.16", + "@vue/shared": "3.5.16" } }, + "node_modules/@vue/compiler-ssr/node_modules/@vue/shared": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", + "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "license": "MIT" + }, "node_modules/@vue/devtools-api": { "version": "7.7.7", "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", @@ -1565,49 +1589,73 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.17.tgz", - "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==", + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.16.tgz", + "integrity": "sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.17" + "@vue/shared": "3.5.16" } }, + "node_modules/@vue/reactivity/node_modules/@vue/shared": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", + "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "license": "MIT" + }, "node_modules/@vue/runtime-core": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.17.tgz", - "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==", + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.16.tgz", + "integrity": "sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/reactivity": "3.5.16", + "@vue/shared": "3.5.16" } }, + "node_modules/@vue/runtime-core/node_modules/@vue/shared": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", + "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "license": "MIT" + }, "node_modules/@vue/runtime-dom": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz", - "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==", + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz", + "integrity": "sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.17", - "@vue/runtime-core": "3.5.17", - "@vue/shared": "3.5.17", + "@vue/reactivity": "3.5.16", + "@vue/runtime-core": "3.5.16", + "@vue/shared": "3.5.16", "csstype": "^3.1.3" } }, + "node_modules/@vue/runtime-dom/node_modules/@vue/shared": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", + "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "license": "MIT" + }, "node_modules/@vue/server-renderer": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.17.tgz", - "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==", + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.16.tgz", + "integrity": "sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-ssr": "3.5.16", + "@vue/shared": "3.5.16" }, "peerDependencies": { - "vue": "3.5.17" + "vue": "3.5.16" } }, + "node_modules/@vue/server-renderer/node_modules/@vue/shared": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", + "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "license": "MIT" + }, "node_modules/@vue/shared": { "version": "3.5.17", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz", @@ -1841,31 +1889,17 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.0.tgz", + "integrity": "sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA==", "license": "MIT", "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", + "browserslist": "^4.17.5", + "caniuse-lite": "^1.0.30001272", + "fraction.js": "^4.1.1", "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" @@ -1873,6 +1907,10 @@ "engines": { "node": "^10 || ^12 || >=14" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, "peerDependencies": { "postcss": "^8.1.0" } @@ -2246,9 +2284,9 @@ } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/debug": { @@ -3440,9 +3478,9 @@ } }, "node_modules/postcss-cli": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-9.1.0.tgz", - "integrity": "sha512-zvDN2ADbWfza42sAnj+O2uUWyL0eRL1V+6giM2vi4SqTR3gTYy8XzcpfwccayF2szcUif0HMmXiEaDv9iEhcpw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-9.0.2.tgz", + "integrity": "sha512-08Wujoy7YGhKCFrGsT9OXqWjtHlGQ+JmyaD/4McjCiwor2IUTRVzXiJd+xmLTGdSWjceS6/TePaJQwBlkVWHiw==", "license": "MIT", "dependencies": { "chokidar": "^3.3.0", @@ -3485,25 +3523,6 @@ "postcss": "^8.0.0" } }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, "node_modules/postcss-load-config": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", @@ -4149,33 +4168,33 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "3.4.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", - "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz", + "integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", - "chokidar": "^3.6.0", + "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.3.2", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.21.6", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", @@ -4209,6 +4228,31 @@ "url": "https://github.com/sponsors/antonk52" } }, + "node_modules/tailwindcss/node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, "node_modules/tailwindcss/node_modules/postcss-load-config": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", @@ -4475,9 +4519,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, "node_modules/unist-util-is": { @@ -4900,16 +4944,16 @@ } }, "node_modules/vue": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.17.tgz", - "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.16.tgz", + "integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.17", - "@vue/compiler-sfc": "3.5.17", - "@vue/runtime-dom": "3.5.17", - "@vue/server-renderer": "3.5.17", - "@vue/shared": "3.5.17" + "@vue/compiler-dom": "3.5.16", + "@vue/compiler-sfc": "3.5.16", + "@vue/runtime-dom": "3.5.16", + "@vue/server-renderer": "3.5.16", + "@vue/shared": "3.5.16" }, "peerDependencies": { "typescript": "*" @@ -4920,6 +4964,12 @@ } } }, + "node_modules/vue/node_modules/@vue/shared": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", + "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 3ec07c067..50211d7d2 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,26 @@ { "name": "documentation", "type": "module", - "dependencies": { - "@types/js-yaml": "^4.0.9", - "autoprefixer": "^10.4.0", - "gray-matter": "^4.0.3", - "js-yaml": "^4.1.1", - "postcss": "^8.3.7", - "postcss-cli": "^9.0.2", - "ts-node": "^10.9.2", - "vitepress-plugin-autobar": "^1.0.8", - "vitepress-sidebar": "^1.31.1", - "@tailwindcss/typography": "^0.5.14", - "@types/markdown-it": "^12.2.3", - "@types/node": "^20.11.27", - "feed": "^4.2.2", - "lodash-es": "^4.17.23", - "@types/lodash-es": "^4.17.12", - "tailwindcss": "^3.4.10", - "tslib": "^2.8.1", - "vitepress": "^1.6.3", - "vitest": "^3.2.4", - "vue": "^3.5.16" - }, - "scripts": { - "docs:dev": "make docs-dev", - "docs:build": "make docs-build", - "docs:preview": "make docs-preview", - "post-process": "make post-process", - "test": "make test", - "test:watch": "make test-watch" + "@types/js-yaml": "4.0.9", + "autoprefixer": "10.4.0", + "gray-matter": "4.0.3", + "js-yaml": "4.1.1", + "postcss": "8.5.6", + "postcss-cli": "9.0.2", + "ts-node": "10.9.2", + "vitepress-plugin-autobar": "1.0.8", + "vitepress-sidebar": "1.31.1", + "@tailwindcss/typography": "0.5.14", + "@types/markdown-it": "12.2.3", + "@types/node": "20.11.27", + "feed": "4.2.2", + "lodash-es": "4.17.23", + "@types/lodash-es": "4.17.12", + "tailwindcss": "3.4.10", + "tslib": "2.8.1", + "vitepress": "1.6.3", + "vitest": "3.2.4", + "vue": "3.5.16" } } diff --git a/post-processing/part-3.js b/post-processing/part-3.js index b2cfc816a..e4e203177 100644 --- a/post-processing/part-3.js +++ b/post-processing/part-3.js @@ -11,10 +11,6 @@ await main(); // Main function async function main() { try { - if (process.argv.includes('--add-empty-metadata') || process.argv.includes('-e')) { - await addEmptyMetadata(BASE_PATH); - } - if (process.argv.includes('--update-report-link') || process.argv.includes('-r')) { await updateReportLink(path.join(BASE_PATH, 'docs/security-and-compliance/report.md')); } @@ -23,19 +19,15 @@ async function main() { await processApiHtml(BASE_PATH); } - if (!process.argv.includes('--add-empty-metadata') && - !process.argv.includes('-e') && - !process.argv.includes('--update-report-link') && + if (!process.argv.includes('--update-report-link') && !process.argv.includes('-r') && !process.argv.includes('--process-api-html') && !process.argv.includes('-a')) { // If no specific action is specified, show usage console.log('Available commands:'); - console.log('--add-empty-metadata, -e : Add isEmpty: true to frontmatter of empty index.md files'); console.log('--update-report-link, -r : Update the report link in security-and-compliance/report.md'); console.log('--process-api-html, -a : Extract HTML content to Vue script setup for API reference files'); - console.log(`\nExample: node post-processing/part-3.js --add-empty-metadata`); - console.log(`Example: node post-processing/part-3.js --update-report-link`); + console.log(`\nExample: node post-processing/part-3.js --update-report-link`); console.log(`Example: node post-processing/part-3.js --process-api-html`); } } catch (err) { @@ -43,156 +35,6 @@ async function main() { } } -/** - * Post-processing function to add isEmpty: true to frontmatter - * of index.md files that have no substantial content - */ -async function addEmptyMetadata(basePath) { - async function findIndexFiles(directory) { - let foundFiles = []; - - try { - const files = await fs.readdir(directory); - - for (const file of files) { - const fullPath = path.join(directory, file); - - try { - const stats = await fs.stat(fullPath); - - if (stats.isDirectory()) { - // Skip ignored directories - if (!IGNORE_DIRS.includes(file)) { - const subdirFiles = await findIndexFiles(fullPath); - foundFiles = foundFiles.concat(subdirFiles); - } - } else if (file === 'index.md') { - foundFiles.push(fullPath); - } - } catch (err) { - console.error(`Error accessing ${fullPath}: ${err.message}`); - } - } - } catch (err) { - console.error(`Error reading directory ${directory}: ${err.message}`); - } - - return foundFiles; - } - - async function processIndexFile(filePath) { - try { - const content = await fs.readFile(filePath, 'utf-8'); - const parsed = matter(content); - - // Check if body is empty (only whitespace, newlines, or comments) - const isEmpty = isBodyEmpty(parsed.content); - - if (isEmpty && parsed.data.isEmpty !== true) { - // Add isEmpty: true to frontmatter - parsed.data.isEmpty = true; - parsed.data.editLink = false; - - // Reconstruct the file with updated frontmatter - const updatedContent = matter.stringify(parsed.content, parsed.data); - await fs.writeFile(filePath, updatedContent, 'utf-8'); - - return { - file: filePath, - modified: true, - action: 'Added isEmpty: true' - }; - } else if (!isEmpty && parsed.data.isEmpty === true) { - // Remove isEmpty if file now has content - delete parsed.data.isEmpty; - const updatedContent = matter.stringify(parsed.content, parsed.data); - await fs.writeFile(filePath, updatedContent, 'utf-8'); - - return { - file: filePath, - modified: true, - action: 'Removed isEmpty (now has content)' - }; - } else if (isEmpty && parsed.data.isEmpty === true) { - return { - file: filePath, - modified: false, - reason: 'Already marked as empty' - }; - } else { - return { - file: filePath, - modified: false, - reason: 'Has content, not empty' - }; - } - } catch (err) { - console.error(`Error processing ${filePath}: ${err.message}`); - return { - file: filePath, - modified: false, - reason: err.message - }; - } - } - - /** - * Check if markdown body content is empty - * Returns true if body only contains whitespace, newlines, comments, or h1 markdown tags - */ - function isBodyEmpty(body) { - if (!body || typeof body !== 'string') { - return true; - } - - // Remove HTML comments - const withoutHtmlComments = body.replace(//g, ''); - - // Remove h1 markdown tags (# Title) - const withoutH1Tags = withoutHtmlComments.replace(/^#\s+.*$/gm, ''); - - // Check if remaining content is only whitespace and newlines - const trimmed = withoutH1Tags.trim(); - - return trimmed === ''; - } - - console.log(`Searching for index.md files in: ${basePath}`); - const indexFiles = await findIndexFiles(basePath); - console.log(`\nFound ${indexFiles.length} index.md files`); - - if (indexFiles.length > 0) { - console.log('\nProcessing index.md files for empty content...'); - const results = []; - - for (const file of indexFiles) { - const result = await processIndexFile(file); - results.push(result); - - if (result.modified) { - console.log(`- ${result.action}: ${path.relative(basePath, file)}`); - } - } - - const modifiedCount = results.filter(r => r.modified).length; - const addedEmptyCount = results.filter(r => r.modified && r.action.includes('Added')).length; - const removedEmptyCount = results.filter(r => r.modified && r.action.includes('Removed')).length; - - console.log(`\nSummary: Modified ${modifiedCount} of ${indexFiles.length} index.md files.`); - if (addedEmptyCount > 0) { - console.log(`- Added isEmpty: true to ${addedEmptyCount} files`); - } - if (removedEmptyCount > 0) { - console.log(`- Removed isEmpty from ${removedEmptyCount} files`); - } - - if (modifiedCount > 0) { - console.log('\nEmpty metadata processing completed!'); - console.log('Files with empty content now have isEmpty: true in their frontmatter.'); - } - } -} - /** * Updates the report link in the security-and-compliance/report.md file * Replaces the download link with a direct view link diff --git a/tests/sidebar-trunk-without-links.test.ts b/tests/sidebar-trunk-without-links.test.ts deleted file mode 100644 index 5b885ce7b..000000000 --- a/tests/sidebar-trunk-without-links.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { describe, it, expect, beforeAll } from 'vitest' -import sidebarData from '../.vitepress/data/sidebar.data.ts' - -interface SidebarLeaf { - text: string - link: string - collapsed?: boolean -} - -interface SidebarBranch { - text: string - items: (SidebarLeaf | SidebarBranch)[] - collapsed?: boolean -} - -type SidebarItem = SidebarLeaf | SidebarBranch - -describe('Sidebar Trunk Without Links Validation', () => { - let sidebarDataResult: any - - beforeAll(async () => { - sidebarDataResult = sidebarData.load() - }) - - const sections = ['usersSidebar', 'developersSidebar', 'operatorsSidebar', 'all'] - - sections.forEach(sectionName => { - it(`${sectionName}: should identify trunks without links (navigation folders)`, () => { - const section = sidebarDataResult[sectionName] - expect(section).toBeDefined() - - const trunksWithoutLinks: string[] = [] - - function analyzeTrunksWithoutLinks(item: SidebarItem, path: string = '') { - const currentPath = path ? `${path} > ${item.text}` : item.text - - // If it's a branch node (has items), check if it lacks a link - if ('items' in item && Array.isArray(item.items)) { - if (!('link' in item) || typeof item.link !== 'string') { - trunksWithoutLinks.push(`${currentPath}`) - } - - // Recursively analyze child items - item.items.forEach(subItem => analyzeTrunksWithoutLinks(subItem, currentPath)) - } - } - - // Handle different section structures - if (Array.isArray(section)) { - section.forEach(item => analyzeTrunksWithoutLinks(item)) - } else if (section && typeof section === 'object') { - if ('items' in section && Array.isArray(section.items)) { - section.items.forEach((item: SidebarItem) => analyzeTrunksWithoutLinks(item)) - } else { - Object.values(section).forEach((item: any) => { - if (item && typeof item === 'object') { - analyzeTrunksWithoutLinks(item) - } - }) - } - } - report(0, sectionName, trunksWithoutLinks) - }) - }) -}) - - -function report(expectation: number, sectionName: string, trunksWithoutLinks: string[]) { - if (trunksWithoutLinks.length > expectation) { - console.log(`\n❌ ${sectionName} - Found ${trunksWithoutLinks.length} trunks without links:`) - trunksWithoutLinks.forEach(trunk => { - console.log(` 📁 ${trunk}`) - }) - console.log(`\n💡 All trunk nodes (branch nodes with items) should have link properties.`) - } else { - console.log(`\n✅ ${sectionName} - No trunks without links found`) - } - expect(trunksWithoutLinks).toHaveLength(expectation) -} \ No newline at end of file diff --git a/website/blog/2025/10/10-22-extensible-advertised-addresses-for-shoots.md b/website/blog/2025/10/10-22-extensible-advertised-addresses-for-shoots.md new file mode 100644 index 000000000..bdb9bc7fa --- /dev/null +++ b/website/blog/2025/10/10-22-extensible-advertised-addresses-for-shoots.md @@ -0,0 +1,57 @@ +--- +title: "Enhanced Endpoint Discovery with Extensible Advertised Addresses" +linkTitle: "Enhanced Endpoint Discovery with Extensible Advertised Addresses" +newsSubtitle: October 22, 2025 +publishdate: 2025-10-22 +authors: +- avatar: https://avatars.githubusercontent.com/dnaeon + email: dnaeon@gmail.com + login: dnaeon + name: Marin Atanasov Nikolov +aliases: ["/blog/2025/10/22/enhanced-endpoint-discovery-with-extensible-advertised-addresses"] +--- + +Gardener has introduced a new feature that enhances the discoverability of services running within a Shoot's control plane. While the `.status.advertisedAddresses` field in the `Shoot` resource has always provided key endpoints like the API server URL, it now supports extension by other components. + +### The Challenge of Endpoint Discovery + +Previously, only a default set of addresses, such as the internal and external API server URLs and the service account issuer URL, were published in a Shoot's status. Endpoints for other essential services deployed in the control plane—like Plutono, Prometheus, or Vali—remained hidden. Discovering these required direct access to the Seed cluster and knowledge of the Shoot's technical ID, making them inaccessible to end-users and external tooling that only interact with the `Shoot` resource. + +### A New Standard for Advertising Endpoints + +To solve this, Gardener now allows any `Ingress` resource in the Shoot's control-plane namespace to be advertised in the `Shoot` status. By applying a simple label, extension developers and operators can make their services easily discoverable. + +When an `Ingress` resource is labeled with `endpoint.shoot.gardener.cloud/advertise=true`, Gardener automatically processes it during the next reconciliation. The hostnames found in the `Ingress`'s TLS configuration are then added as new entries to the `.status.advertisedAddresses` list. + +For example, to advertise the endpoint for a Plutono dashboard, you would apply the label to its `Ingress` resource: +```bash +kubectl --namespace shoot---- \ + label ingress plutono endpoint.shoot.gardener.cloud/advertise=true +``` + +After the Shoot reconciles, the list of advertised addresses will include a new entry for Plutono. The name is automatically generated to ensure uniqueness, following the pattern `ingress///`. + +A typical list of advertised addresses might look like this after the change: +```yaml +- name: external + url: https://api.my-shoot.external.gardener.cloud +- name: internal + url: https://api.my-shoot.internal.gardener.cloud +- name: service-account-issuer + url: https://discovery.gardener.cloud/projects/my-project/shoots/shoot-id/issuer +- name: ingress/plutono/0/0 + url: https://gu-my-shoot.ingress.seed.gardener.cloud +``` + +This mechanism provides a standardized way for any component, including custom extensions, to publish its endpoints directly on the `Shoot` object, making them programmatically discoverable for users and automation. + +### Future Enhancements + +This feature currently supports `Ingress` resources, with plans to include support for `Gateway` resources from the Kubernetes Gateway API in the future. + +### Find Out More + +To learn more about this feature, you can explore the following resources: +* [Watch the original presentation](https://youtu.be/GArG1wh2j1o?t=432) +* [Review the implementation on GitHub](https://github.com/gardener/gardener/pull/13043) +* [Read the developer documentation](https://github.com/gardener/gardener/blob/main/docs/development/shoot-advertised-addresses.md) \ No newline at end of file diff --git a/website/blog/2026/01/01-28-introducing-automated-credential-rotation.md b/website/blog/2026/01/01-28-introducing-automated-credential-rotation.md new file mode 100644 index 000000000..cb6b2fc11 --- /dev/null +++ b/website/blog/2026/01/01-28-introducing-automated-credential-rotation.md @@ -0,0 +1,58 @@ +--- +title: "Introducing Automated Credential Rotation" +linkTitle: "Introducing Automated Credential Rotation" +newsSubtitle: January 28, 2026 +publishdate: 2026-01-28 +authors: +- avatar: https://avatars.githubusercontent.com/AleksandarSavchev + login: AleksandarSavchev + name: Aleksandar Savchev +aliases: ["/blog/2026/01/28/introducing-automated-credential-rotation"] +--- + +Maintaining a strong security posture is crucial for any Kubernetes environment. A key aspect of this is the regular rotation of credentials. To simplify this essential task and reduce operational overhead, Gardener now supports the automatic rotation of several critical credentials during a `Shoot` cluster's maintenance window. + +### Enhanced Security, Effortlessly + +Previously, users were responsible for manually triggering credential rotations. With this new enhancement, you can now configure your `Shoot` clusters to automatically handle the rotation of: + +* **SSH keypair** for worker nodes +* **Observability passwords** +* **etcd encryption key** + +This ensures that credentials are rotated consistently and on schedule, bolstering the security of your clusters without requiring manual intervention. + +### How to Enable Automatic Rotation + +You can opt-in to this feature by defining the desired rotation schedule in the `Shoot` manifest under the `.spec.maintenance.autoRotation` field. + +During the daily maintenance window, the `gardener-controller-manager` will check if the configured rotation period has passed since the last successful rotation for a given credential. If it has, a new rotation will be initiated automatically. + +Here is an example of how to configure it: + +```yaml +spec: + maintenance: + autoRotation: + credentials: + # Set this field to enable automatic rotation for observability credentials + observability: + rotationPeriod: 168h # Rotates every 7 days + # Set this field to enable automatic rotation for the SSH keypair + sshKeypair: + rotationPeriod: 168h # Rotates every 7 days + # Set this field to enable automatic rotation for the etcd encryption key + etcdEncryptionKey: + rotationPeriod: 168h # Rotates every 7 days +``` + +If you specify a credential type (like `observability: {}`) but omit the `rotationPeriod`, it will default to `168h` (7 days). The rotation period can be configured to be between 30 minutes and 90 days. + +To disable automatic rotation for a specific credential, you can set its `rotationPeriod` to `0`. Manual rotation via annotations remains available if you need to trigger a rotation outside of the scheduled maintenance window. + +This new capability makes it easier than ever to follow security best practices, helping you keep your Gardener-managed Kubernetes clusters secure and up-to-date. + +### Further Reading + +* [Recording of "Automatic Credentials Rotation During Shoot Maintenance"](https://youtu.be/2rOOsQWLO_w) +* [GitHub Pull Request: Add option to automatically rotate credentials in the Maintenance window](https://github.com/gardener/gardener/pull/13493) \ No newline at end of file diff --git a/website/blog/2026/02/02-18-enhanced-security-for-helm-deployments-gardener-adds-custom-ca-support-for-oci-registries.md b/website/blog/2026/02/02-18-enhanced-security-for-helm-deployments-gardener-adds-custom-ca-support-for-oci-registries.md new file mode 100644 index 000000000..d308a351d --- /dev/null +++ b/website/blog/2026/02/02-18-enhanced-security-for-helm-deployments-gardener-adds-custom-ca-support-for-oci-registries.md @@ -0,0 +1,115 @@ +--- +title: "Enhanced Security for Helm Deployments: Gardener Adds Custom CA Support for OCI Registries" +linkTitle: "Enhanced Security for Helm Deployments: Gardener Adds Custom CA Support for OCI Registries" +newsSubtitle: February 18, 2026 +publishdate: 2026-02-18 +authors: +- avatar: https://avatars.githubusercontent.com/shafeeqes + email: shafeeque.e.s@sap.com + login: shafeeqes + name: Shafeeque E S +aliases: ["/blog/2026/02/18/enhanced-security-for-helm-deployments-gardener-adds-custom-ca-support-for-oci-registries"] +--- + +Gardener continues to enhance its security and flexibility, particularly for users operating in air-gapped environments or utilizing private infrastructure. A new feature now allows operators to specify a custom Certificate Authority (CA) bundle when pulling Helm charts from OCI registries. This is a significant improvement for environments where registries are secured with custom or self-signed TLS certificates. + +## The Challenge of Private Registries + +Previously, while Gardener supported authentication to private OCI registries using pull secrets, it lacked a way to establish trust with registries secured by custom TLS certificate chains. This prevented Helm charts for extensions and other components from being downloaded due to certificate verification failures. + +### A New Level of Trust: `caBundleSecretRef` + +To solve this, a new optional field, `caBundleSecretRef`, has been added to the `ociRepository` configuration in the following resources: +- `operator.gardener.cloud/v1alpha1.Extension` +- `core.gardener.cloud/v1beta1.ControllerDeployment` +- `core.gardener.cloud/v1.ControllerDeployment` + +This field allows you to reference a Kubernetes Secret that contains the necessary CA bundle to verify the OCI registry's TLS certificate. + +### How It Works + +The process is straightforward: + +1. **Create a CA Bundle Secret**: First, you create a standard Kubernetes Secret in the `garden` namespace. For the `Extension` resource, create this secret in the runtime garden cluster; for `ControllerDeployment`, create it in the virtual garden cluster. This secret must contain the PEM-encoded CA certificate bundle under the data key `bundle.crt`. + +2. **Label the Secret**: For the `gardenlet` to use this secret, it must be labeled with `gardener.cloud/role: oci-ca-bundle`. This label allows the secret to be propagated to the seed namespace in the virtual garden. + +3. **Reference the Secret**: Finally, you reference this secret by name in the `caBundleSecretRef` field within the `ociRepository` block of your `Extension` or `ControllerDeployment` manifest. + +Here is an example of how to configure an extension to use a private OCI registry with a custom CA: + +First, create the secret containing your CA bundle: +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-registry-ca + namespace: garden + labels: + gardener.cloud/role: oci-ca-bundle +type: Opaque +data: + bundle.crt: +``` +Next, reference this secret in your `Extension` definition: +```yaml +apiVersion: operator.gardener.cloud/v1alpha1 +kind: Extension +metadata: + name: provider-example +spec: + deployment: + admission: + runtimeCluster: + helm: + ociRepository: + repository: registry.example.com/charts/admission-runtime + tag: v1.0.0 + caBundleSecretRef: + name: my-registry-ca + pullSecretRef: + name: my-pull-secret + virtualCluster: + helm: + ociRepository: + repository: registry.example.com/charts/admission-application + tag: v1.0.0 + caBundleSecretRef: + name: my-registry-ca + pullSecretRef: + name: my-pull-secret + extension: + helm: + ociRepository: + repository: registry.example.com/charts/extension + tag: v1.0.0 + caBundleSecretRef: + name: my-registry-ca + pullSecretRef: + name: my-pull-secret +``` +or `ControllerDeployment` definition: +```yaml +apiVersion: core.gardener.cloud/v1beta1 +kind: ControllerDeployment +metadata: + name: provider-example +spec: + helm: + ociRepository: + repository: registry.example.com/charts/controller + tag: v1.0.0 + caBundleSecretRef: + name: my-registry-ca + pullSecretRef: + name: my-pull-secret +``` + +With this configuration, Gardener will use the provided CA bundle to securely pull the Helm chart, enabling seamless deployment of extensions from your private registries. + +This feature is the first step in a broader effort to improve support for custom CAs. Future updates will extend this capability, for example, to support the `gardener-node-agent` image to be pulled from a private registry during node bootstrapping. + +--- +### Explore Further +- [Recording of the presentation](https://youtu.be/jScp5zha7Fc?t=1922) +- [GitHub Pull Request #13868](https://github.com/gardener/gardener/pull/13868) \ No newline at end of file diff --git a/website/blog/2026/02/02-18-seamlessly-switch-calicos-overlay-network-in-gardener.md b/website/blog/2026/02/02-18-seamlessly-switch-calicos-overlay-network-in-gardener.md new file mode 100644 index 000000000..5c091ee12 --- /dev/null +++ b/website/blog/2026/02/02-18-seamlessly-switch-calicos-overlay-network-in-gardener.md @@ -0,0 +1,46 @@ +--- +title: "Seamlessly Switch Calico's Overlay Network in Gardener" +linkTitle: "Seamlessly Switch Calico's Overlay Network in Gardener" +newsSubtitle: February 18, 2026 +publishdate: 2026-02-18 +authors: +- avatar: https://avatars.githubusercontent.com/DockToFuture + email: sebastian.stauch@sap.com + login: DockToFuture + name: Sebastian Stauch +aliases: ["/blog/2026/02/18/seamlessly-switch-calicos-overlay-network-in-gardener"] +--- + +Switching networking configurations in a live Kubernetes cluster is a delicate operation where timing is everything. A common scenario for Gardener operators is transitioning a cluster's Calico networking from an overlay mode (like IPIP) to a non-overlay, native routing mode. Previously, this switch could lead to temporary network disruptions. We're happy to announce a new feature that ensures this transition is seamless and free of downtime. + +### The Challenge: A Race for Routes + +In a non-overlay mode, Calico relies on the cloud provider's infrastructure to route traffic between nodes. This is typically handled by a route controller that creates the necessary entries in the provider's route tables. + +The problem was a potential race condition. When an operator disabled the overlay network, the Calico daemonset would begin to roll and reconfigure itself. If this happened *before* the route controller had finished creating the required routes for all nodes, pods could temporarily lose the ability to communicate with each other, resulting in a period of network downtime. This was especially problematic in large clusters, where route creation can take time or even fail due to cloud provider route table quotas. + +### The Solution: A Coordinated, Seamless Switch + +To solve this, Gardener now introduces the `SeamlessOverlaySwitch`, an alpha feature gate in the `gardener-extension-networking-calico`. When enabled, this feature orchestrates the transition to ensure the underlying network is ready before Calico is reconfigured. + +This is made possible by a foundational enhancement to the `aws-custom-route-controller`. The controller now updates the standard `NetworkUnavailable` condition on each Kubernetes node. When it successfully creates the required routes for a node, it sets this condition to `False` with the reason `RouteCreated`. + +With this crucial signal in place, the Calico extension can now perform a coordinated switch: + +1. An operator initiates the switch by disabling the overlay in the Shoot specification. +2. With the `SeamlessOverlaySwitch` feature enabled, the Calico extension detects the requested change but pauses the reconfiguration. +3. It begins monitoring the `NetworkUnavailable` condition on all nodes in the cluster. +4. The extension waits until it has verified that **all** nodes are reporting `NetworkUnavailable=False` with the reason `RouteCreated`. +5. Only once this confirmation is received does the extension proceed with rolling the Calico daemonset to the new non-overlay configuration. + +If the routes are not yet ready on all nodes, the reconciliation process is safely retried, and the existing overlay network remains active and uninterrupted. This guarantees a zero-downtime transition and prevents the cluster from entering a partially configured, inconsistent state. This enhancement is currently available for AWS, with support for other cloud providers to follow. + +> **Side note:** The feature relies on mutating admission policies, which need to be enabled for the shoot cluster. + +*** + +### Further Reading + +* [Recording: Seamless Overlay Network Switch](https://youtu.be/jScp5zha7Fc?t=1591) +* [Pull Request: Add `SeamlessOverlaySwitch` feature gate](https://github.com/gardener/gardener-extension-networking-calico/pull/779) +* [Pull Request: Set network unavailable condition](https://github.com/gardener/aws-custom-route-controller/pull/411) \ No newline at end of file diff --git a/website/blog/2026/02/02-18-simplify-multi-cluster-configuration-with-static-manifest-propagation.md b/website/blog/2026/02/02-18-simplify-multi-cluster-configuration-with-static-manifest-propagation.md new file mode 100644 index 000000000..84511a3bd --- /dev/null +++ b/website/blog/2026/02/02-18-simplify-multi-cluster-configuration-with-static-manifest-propagation.md @@ -0,0 +1,100 @@ +--- +title: "Simplify Multi-Cluster Configuration with Static Manifest Propagation" +linkTitle: "Simplify Multi-Cluster Configuration with Static Manifest Propagation" +newsSubtitle: February 18, 2026 +publishdate: 2026-02-18 +authors: +- avatar: https://avatars.githubusercontent.com/rfranzke + email: rafael.franzke@sap.com + login: rfranzke + name: Rafael Franzke +aliases: ["/blog/2026/02/18/simplify-multi-cluster-configuration-with-static-manifest-propagation"] +--- + +Managing configurations consistently across a fleet of Kubernetes clusters can be a complex task. Operators often need a straightforward way to deploy baseline resources—such as security policies, resource quotas, or RBAC rules—to all or a subset of their clusters without the overhead of building and maintaining a full-blown extension. + +Gardener now introduces a new feature that directly addresses this need: **Static Manifest Propagation from Seeds to Shoots**. This enhancement provides a declarative, centralized mechanism for distributing Kubernetes manifests to Shoot clusters. + +### How It Works + +The mechanism is simple yet powerful. During a Shoot reconciliation, the `gardenlet` component scans the `garden` namespace in its Seed cluster for any `Secret` resources labeled with `gardener.cloud/purpose=shoot-static-manifest`. + +For each `Secret` it finds, `gardenlet` performs the following actions: +1. It copies the `Secret` into the Shoot's control plane namespace within the Seed. +2. It creates or updates a single `ManagedResource` that references all such `Secret`s. +3. The `gardener-resource-manager` in the Shoot then ensures that the manifests contained within these `Secret`s are applied to the Shoot cluster. + +This process runs automatically on every reconciliation, ensuring that the configurations in the Shoot clusters remain synchronized with the source manifests defined in the Seed. + +### Getting Started: A Practical Example + +To propagate a static manifest, such as a `ResourceQuota` or a `ClusterRole`, you simply create a `Secret` in the Seed's `garden` namespace. + +1. **Prepare your manifests** in a YAML file. + ```yaml + # my-manifests.yaml + --- + apiVersion: v1 + kind: ResourceQuota + metadata: + name: default-quota + namespace: default + spec: + hard: + pods: "50" + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: org-viewer + rules: + - apiGroups: [""] + resources: ["pods", "services"] + verbs: ["get", "list", "watch"] + ``` + +2. **Create a labeled `Secret`** in the `garden` namespace of your Seed cluster from this file. + ```bash + kubectl create secret generic my-static-manifests \ + --from-file=manifests.yaml=my-manifests.yaml \ + --namespace=garden \ + --dry-run=client -o yaml | \ + kubectl label --local -f - gardener.cloud/purpose=shoot-static-manifest | \ + kubectl apply -f - + ``` +Once applied, the next reconciliation of any Shoot on that Seed will deploy these resources. Updating the `Secret` in the `garden` namespace will automatically roll out the changes, and deleting it will clean up the resources from the Shoots. + +### Targeting Specific Shoots + +By default, manifests are propagated to all Shoot clusters on a given Seed. However, you can target specific Shoots by adding the `static-manifests.shoot.gardener.cloud/selector` annotation to your `Secret`. This annotation takes a standard Kubernetes label selector in JSON format, which is matched against the labels of the Shoot resource. + +For example, to apply a manifest only to Shoots with the label `environment: production`, you would annotate your `Secret` like this: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: production-only-manifests + namespace: garden + labels: + gardener.cloud/purpose: shoot-static-manifest + annotations: + static-manifests.shoot.gardener.cloud/selector: | + {"matchLabels":{"environment":"production"}} +data: + # ... your base64-encoded manifest data +``` + +### Important Considerations + +- **Static Only**: As the name implies, this feature is for purely static manifests. No templating or dynamic value injection is supported. For more complex scenarios, creating a Gardener extension remains the recommended approach. +- **Resource Conflicts**: Operators are responsible for ensuring that propagated manifests do not conflict with resources managed by Gardener or other extensions. +- **Health and Visibility**: If any resource deployed via this mechanism fails to apply or become healthy, the issue will be reflected in the `ManagedResource` status and will cause the Shoot's `SystemComponentsHealthy` condition to become `False`, providing clear visibility for operators. + +This new feature empowers operators to enforce consistency and manage baseline configurations across their landscape of Shoot clusters with greater ease and efficiency. + +### Further Reading + +- [Recording of the Presentation](https://youtu.be/jScp5zha7Fc?t=714) +- [Pull Request #13614](https://github.com/gardener/gardener/pull/13614) +- [Official Documentation](https://github.com/gardener/gardener/tree/master/docs/extensions/static-manifests.md) \ No newline at end of file diff --git a/website/blog/index.md b/website/blog/index.md index 5074d0c22..ba0f26475 100644 --- a/website/blog/index.md +++ b/website/blog/index.md @@ -1,11 +1,11 @@ --- -title: "Blogs" +title: "Blog" editLink: false outline: false aside: false --- -# Blogs +# Blog