From 01af9219f089d092ae6d30d1079c5936b2c7643d Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:37:55 -0700 Subject: [PATCH] feat: use pre-generated tutorials map (#2401) --- package.json | 1 + .../helpers/get-mdx-links-to-rewrite.ts | 3 + .../rewrite-links-plugin.ts | 10 +- .../rewrite-links.ts | 2 + scripts/generate-tutorial-map.ts | 42 + src/data/_tutorial-map.generated.json | 977 ++++++++++++++++++ .../utils/prepare-nav-data-for-client.ts | 13 +- src/lib/learn-client/index.ts | 13 - .../__tests__/rewrite-tutorial-links.test.ts | 62 +- .../rewrite-tutorial-links/index.ts | 12 +- .../utils/get-tutorial-map.ts | 43 - .../rewrite-tutorial-links/utils/index.ts | 1 - src/lib/sitemap/tutorials-content-fields.ts | 4 +- src/pages/api/tutorials-map.ts | 57 - src/views/docs-view/server.ts | 4 +- .../detect-and-reformat-learn-url.test.ts | 32 +- .../helpers/detect-and-reformat-learn-url.ts | 8 +- 17 files changed, 1088 insertions(+), 196 deletions(-) create mode 100644 scripts/generate-tutorial-map.ts create mode 100644 src/data/_tutorial-map.generated.json delete mode 100644 src/lib/remark-plugins/rewrite-tutorial-links/utils/get-tutorial-map.ts delete mode 100644 src/pages/api/tutorials-map.ts diff --git a/package.json b/package.json index 0c7d2f3379..085ba95ba8 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "build:webpack-only": "npm run prebuild && hc-tools next-build-webpack-only", "build": "next build", "clean": "rimraf .next node_modules", + "generate:tutorial-map": "hc-tools ./scripts/generate-tutorial-map.ts", "lint": "next-hashicorp lint", "lint:stylelint": "npx stylelint '**/*.css'", "prebuild": "hc-tools ./scripts/generate-tutorial-variant-map.ts && hc-tools ./scripts/extract-hvd-content.ts", diff --git a/scripts/docs-content-link-rewrites/helpers/get-mdx-links-to-rewrite.ts b/scripts/docs-content-link-rewrites/helpers/get-mdx-links-to-rewrite.ts index e12c126db7..89f8730550 100644 --- a/scripts/docs-content-link-rewrites/helpers/get-mdx-links-to-rewrite.ts +++ b/scripts/docs-content-link-rewrites/helpers/get-mdx-links-to-rewrite.ts @@ -13,11 +13,13 @@ const getMdxLinksToRewrite = async ({ filePaths, urlAdjustFn, repo, + tutorialMap, }: { filePathPrefix: string filePaths: string[] urlAdjustFn: (url: string) => string repo: string + tutorialMap: Record }): Promise<{ mdxLinksToRewrite: Record> mdxUnrewriteableLinks: Record> @@ -73,6 +75,7 @@ const getMdxLinksToRewrite = async ({ currentPath, statistics: data, urlAdjustFn, + tutorialMap, }) .process(fileContent) diff --git a/scripts/docs-content-link-rewrites/rewrite-links-plugin.ts b/scripts/docs-content-link-rewrites/rewrite-links-plugin.ts index ee94d69d2e..7f3a9df4cc 100644 --- a/scripts/docs-content-link-rewrites/rewrite-links-plugin.ts +++ b/scripts/docs-content-link-rewrites/rewrite-links-plugin.ts @@ -8,14 +8,12 @@ import { Plugin } from 'unified' import visit from 'unist-util-visit' import { processDocsLinkNode } from 'lib/remark-plugins/remark-plugin-adjust-link-urls/helpers' import { rewriteTutorialsLink } from 'lib/remark-plugins/rewrite-tutorial-links/utils/rewrite-tutorials-link' -import { getTutorialMap } from 'lib/remark-plugins/rewrite-tutorial-links/utils' - -let TUTORIAL_MAP const rewriteLinksPlugin: Plugin = ({ urlAdjustFn, currentPath, statistics = { linksToRewrite: {}, unrewriteableLinks: [] }, + tutorialMap, }: { currentPath: string statistics?: { @@ -23,11 +21,9 @@ const rewriteLinksPlugin: Plugin = ({ unrewriteableLinks: string[] } urlAdjustFn: (url: string) => string + tutorialMap: Record }) => { return async function transformer(tree) { - if (!TUTORIAL_MAP) { - TUTORIAL_MAP = await getTutorialMap() - } return visit(tree, ['link', 'definition'], (node: Link | Definition) => { const originalUrl = `${node.url}` @@ -43,7 +39,7 @@ const rewriteLinksPlugin: Plugin = ({ // Then apply changes on top of that with the tutorials link rewriter node.url = rewriteTutorialsLink( processedAdDocsLink, - TUTORIAL_MAP, + tutorialMap, 'docs' ) } diff --git a/scripts/docs-content-link-rewrites/rewrite-links.ts b/scripts/docs-content-link-rewrites/rewrite-links.ts index 062933c870..c1a662b059 100644 --- a/scripts/docs-content-link-rewrites/rewrite-links.ts +++ b/scripts/docs-content-link-rewrites/rewrite-links.ts @@ -5,6 +5,7 @@ import fs from 'fs' import path from 'path' +import tutorialMap from 'data/_tutorial-map.generated.json' import { normalizeRemoteLoaderSlug } from 'lib/docs-content-link-rewrites/normalize-remote-loader-slug' import { cachedGetProductData } from 'lib/get-product-data' import { getProductUrlAdjuster } from 'views/docs-view/utils/product-url-adjusters' @@ -52,6 +53,7 @@ const main = async () => { filePaths: changedMdxFiles, urlAdjustFn, repo, + tutorialMap, }) // Handle files that contain links that need to be rewritten. diff --git a/scripts/generate-tutorial-map.ts b/scripts/generate-tutorial-map.ts new file mode 100644 index 0000000000..73cca195b8 --- /dev/null +++ b/scripts/generate-tutorial-map.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { writeFile } from 'node:fs/promises' +import path from 'node:path' +import { getTutorialSlug } from '../src/views/collection-view/helpers' +import { getAllTutorials } from '../src/lib/learn-client/api/tutorial' + +// We specifically want to generate the tutorial map from the production +// API, so we set the API URL explicitly since local development will typically +// be using the staging API. +process.env.NEXT_PUBLIC_LEARN_API_BASE_URL = + 'https://2mz7e9hai3.us-east-1.awsapprunner.com' + +/** + * This function creates a map of 'database-slug': 'dev-dot/path' + */ +async function generateTutorialMap(): Promise> { + const allTutorials = await getAllTutorials({ + fullContent: false, + slugsOnly: true, + }) + + const mapItems = allTutorials.map((t) => { + const oldPath = t.slug + const newPath = getTutorialSlug(t.slug, t.collection_slug) + return [oldPath, newPath] + }) + + return Object.fromEntries(mapItems) +} + +;(async () => { + const tutorialMap = await generateTutorialMap() + await writeFile( + path.join('src', 'data', '_tutorial-map.generated.json'), + JSON.stringify(tutorialMap, null, 2), + 'utf-8' + ) +})() diff --git a/src/data/_tutorial-map.generated.json b/src/data/_tutorial-map.generated.json new file mode 100644 index 0000000000..9a5ced635c --- /dev/null +++ b/src/data/_tutorial-map.generated.json @@ -0,0 +1,977 @@ +{ + "consul/associate-study-003": "/consul/tutorials/certification-003/associate-study-003", + "consul/associate-review-003": "/consul/tutorials/certification-003/associate-review-003", + "consul/associate-questions-003": "/consul/tutorials/certification-003/associate-questions-003", + "cloud/vault-radar-secret-scanning": "/vault/tutorials/radar-foundations/vault-radar-secret-scanning", + "cloud/vault-radar-ticketing-setup": "/vault/tutorials/radar-foundations/vault-radar-ticketing-setup", + "cloud/vault-radar-introduction": "/vault/tutorials/radar-foundations/vault-radar-introduction", + "cloud/vault-radar-alert-setup": "/vault/tutorials/radar-foundations/vault-radar-alert-setup", + "consul/monitor-applications-health-checks": "/consul/tutorials/developer-discovery/monitor-applications-health-checks", + "well-architected-framework/verify-hashicorp-binary": "/well-architected-framework/operational-excellence/verify-hashicorp-binary", + "consul/terminating-gateway": "/consul/tutorials/service-mesh-traffic-management/terminating-gateway", + "consul/access-control-manage-permissions": "/consul/tutorials/security/access-control-manage-permissions", + "consul/consul-template-load-balancing": "/consul/tutorials/developer-configuration/consul-template-load-balancing", + "nomad/integration-gcp": "/nomad/tutorials/fed-workload-identity/integration-gcp", + "terraform/pro-study": "/terraform/tutorials/pro-cert/pro-study", + "cloud/kubernetes-vso-hcp-vault": "/vault/tutorials/cloud-ops/kubernetes-vso-hcp-vault", + "consul/server-metrics-and-logs": "/consul/tutorials/service-mesh-observability/server-metrics-and-logs", + "boundary/oidc-okta": "/boundary/tutorials/identity-management/oidc-okta", + "boundary/oidc-auth0": "/boundary/tutorials/identity-management/oidc-auth0", + "boundary/oidc-azure": "/boundary/tutorials/identity-management/oidc-azure", + "consul/proxy-access-logs": "/consul/tutorials/service-mesh-observability/proxy-access-logs", + "nomad/job-spec-actions": "/nomad/tutorials/job-specifications/job-spec-actions", + "nomad/vault-acl": "/nomad/tutorials/integrate-vault/vault-acl", + "nomad/consul-acl": "/nomad/tutorials/integrate-consul/consul-acl", + "nomad/nomad-pack-detailed-usage": "/nomad/tutorials/nomad-pack/nomad-pack-detailed-usage", + "consul/proxy-metrics": "/consul/tutorials/service-mesh-observability/proxy-metrics", + "onboarding/vault-metrics-new-relic": "/onboarding/hcp-vault-week-7/vault-metrics-new-relic", + "onboarding/vault-metrics-http": "/onboarding/hcp-vault-week-7/vault-metrics-http", + "onboarding/vault-audit-log-new-relic": "/onboarding/hcp-vault-week-7/vault-audit-log-new-relic", + "onboarding/vault-audit-log-http": "/onboarding/hcp-vault-week-7/vault-audit-log-http", + "nomad/sso-oidc-auth0": "/nomad/tutorials/access-control/sso-oidc-auth0", + "consul/secure-services-intentions": "/consul/tutorials/service-mesh-security/secure-services-intentions", + "consul/secure-services-intentions-l7": "/consul/tutorials/service-mesh-security/secure-services-intentions-l7", + "well-architected-framework/operational-excellence-migrate-unmanaged-vault-namespace-to-terraform-managed-infra": "/well-architected-framework/operational-excellence/operational-excellence-migrate-unmanaged-vault-namespace-to-terraform-managed-infra", + "well-architected-framework/operational-excellence-managing-vault-with-terraform": "/well-architected-framework/operational-excellence/operational-excellence-managing-vault-with-terraform", + "vault/benchmark-vault": "/vault/tutorials/operations/benchmark-vault", + "waypoint/use-template": "/waypoint/tutorials/hcp-waypoint/use-template", + "waypoint/set-up-rbac": "/waypoint/tutorials/hcp-waypoint/set-up-rbac", + "waypoint/install-addon": "/waypoint/tutorials/hcp-waypoint/install-addon", + "waypoint/get-started-hcp": "/waypoint/tutorials/hcp-waypoint/get-started-hcp", + "waypoint/destroy-project-resources": "/waypoint/tutorials/hcp-waypoint/destroy-project-resources", + "waypoint/create-addon": "/waypoint/tutorials/hcp-waypoint/create-addon", + "waypoint/create-template": "/waypoint/tutorials/hcp-waypoint/create-template", + "boundary/aws-session-rec-vault": "/boundary/tutorials/host-management/aws-session-rec-vault", + "boundary/ldap-auth": "/boundary/tutorials/identity-management/ldap-auth", + "consul/hashiconf-2023": "/consul/tutorials/service-mesh-observability/hashiconf-2023", + "terraform/test": "/terraform/tutorials/configuration-language/test", + "cloud/kubernetes-vso": "/vault/tutorials/hcp-vault-secrets-get-started/kubernetes-vso", + "vault/secrets-sync": "/vault/tutorials/enterprise/secrets-sync", + "vault/saml": "/vault/tutorials/auth-methods/saml", + "vault/pki-cieps": "/vault/tutorials/enterprise/pki-cieps", + "consul/virtual-machine-gs-manage": "/consul/tutorials/get-started-vms/virtual-machine-gs-manage", + "consul/service-splitters-canary-deployment": "/consul/tutorials/developer-mesh/service-splitters-canary-deployment", + "boundary/kubernetes-getting-started-setup": "/boundary/tutorials/kubernetes-connect/kubernetes-getting-started-setup", + "boundary/kubernetes-getting-started-intro": "/boundary/tutorials/kubernetes-connect/kubernetes-getting-started-intro", + "boundary/kubernetes-getting-started-config": "/boundary/tutorials/kubernetes-connect/kubernetes-getting-started-config", + "boundary/kubernetes-getting-started-connect": "/boundary/tutorials/kubernetes-connect/kubernetes-getting-started-connect", + "consul/consul-ecs": "/consul/tutorials/cloud-integrations/consul-ecs", + "onboarding/vault-metrics-elastic": "/onboarding/hcp-vault-week-7/vault-metrics-elastic", + "onboarding/vault-metrics-cloudwatch": "/onboarding/hcp-vault-week-7/vault-metrics-cloudwatch", + "onboarding/vault-audit-log-elastic": "/onboarding/hcp-vault-week-7/vault-audit-log-elastic", + "onboarding/vault-audit-log-cloudwatch": "/onboarding/hcp-vault-week-7/vault-audit-log-cloudwatch", + "terraform/azure-virtual-machine-scale-sets": "/terraform/tutorials/it-saas/azure-virtual-machine-scale-sets", + "well-architected-framework/security-protecting-secrets-outside-of-vault": "/well-architected-framework/security/security-protecting-secrets-outside-of-vault", + "consul/consul-terraform-sync-zscaler-application-segments": "/consul/tutorials/network-infrastructure-automation/consul-terraform-sync-zscaler-application-segments", + "cloud/hcp-vault-secrets-terraform": "/vault/tutorials/hcp-vault-secrets-get-started/hcp-vault-secrets-terraform", + "well-architected-framework/operational-excellence-workspaces-projects": "/well-architected-framework/operational-excellence/operational-excellence-workspaces-projects", + "well-architected-framework/reliability-vault-monitoring-key-metrics": "/well-architected-framework/reliability/reliability-vault-monitoring-key-metrics", + "vault/pki-acme-caddy": "/vault/tutorials/secrets-management/pki-acme-caddy", + "vault/agent-env-vars": "/vault/tutorials/vault-agent/agent-env-vars", + "cloud/hcp-vault-secrets-retrieve-secret": "/vault/tutorials/hcp-vault-secrets-get-started/hcp-vault-secrets-retrieve-secret", + "cloud/hcp-vault-secrets-introduction": "/vault/tutorials/hcp-vault-secrets-get-started/hcp-vault-secrets-introduction", + "cloud/hcp-vault-secrets-install-cli": "/vault/tutorials/hcp-vault-secrets-get-started/hcp-vault-secrets-install-cli", + "cloud/hcp-vault-secrets-create-secret": "/vault/tutorials/hcp-vault-secrets-get-started/hcp-vault-secrets-create-secret", + "boundary/ent-reference-architecture": "/boundary/tutorials/enterprise/ent-reference-architecture", + "boundary/ent-deployment-guide": "/boundary/tutorials/enterprise/ent-deployment-guide", + "terraform/dynamic-credentials-no-code": "/terraform/tutorials/cloud/dynamic-credentials-no-code", + "consul/permissive-mtls": "/consul/tutorials/kubernetes/permissive-mtls", + "consul/hashidays-2023": "/consul/tutorials/resiliency/hashidays-2023", + "terraform/checks": "/terraform/tutorials/configuration-language/checks", + "consul/virtual-machine-gs-service-mesh-access": "/consul/tutorials/get-started-vms/virtual-machine-gs-service-mesh-access", + "vault/google-workspace-oauth": "/vault/tutorials/auth-methods/google-workspace-oauth", + "vault/secure-confluent-logs": "/vault/tutorials/app-integration/secure-confluent-logs", + "vault/pgp-encrypted-key-shares": "/vault/tutorials/operations/pgp-encrypted-key-shares", + "terraform/dynamic-credentials-vault": "/terraform/tutorials/cloud/dynamic-credentials-vault", + "terraform/kubernetes-operator-v2": "/terraform/tutorials/kubernetes/kubernetes-operator-v2", + "terraform/kubernetes-operator-v2-agentpool": "/terraform/tutorials/kubernetes/kubernetes-operator-v2-agentpool", + "well-architected-framework/operational-excellence-terraform-maturity": "/well-architected-framework/operational-excellence/operational-excellence-terraform-maturity", + "terraform/drift-detection": "/terraform/tutorials/cloud/drift-detection", + "consul/kubernetes-windows-nodes": "/consul/tutorials/kubernetes/kubernetes-windows-nodes", + "vault/vault-secrets-operator": "/vault/tutorials/kubernetes/vault-secrets-operator", + "cloud/kubernetes-hcp-vault": "/vault/tutorials/cloud-ops/kubernetes-hcp-vault", + "vault/troubleshoot-tune-enterprise-replication": "/vault/tutorials/enterprise/troubleshoot-tune-enterprise-replication", + "well-architected-framework/security-vault-anti-patterns": "/well-architected-framework/operational-excellence/security-vault-anti-patterns", + "vault/azure-root-cred-rotate": "/vault/tutorials/secrets-management/azure-root-cred-rotate", + "nomad/gs-start-a-cluster": "/nomad/tutorials/get-started/gs-start-a-cluster", + "nomad/gs-stop-nomad": "/nomad/tutorials/get-started/gs-stop-nomad", + "nomad/gs-overview": "/nomad/tutorials/get-started/gs-overview", + "nomad/gs-install": "/nomad/tutorials/get-started/gs-install", + "nomad/gs-deploy-job": "/nomad/tutorials/get-started/gs-deploy-job", + "terraform/servicenow-sgc": "/terraform/tutorials/it-saas/servicenow-sgc", + "vault/gcp-auth-method": "/vault/tutorials/auth-methods/gcp-auth-method", + "vault/kubernetes-rhdp": "/vault/tutorials/kubernetes/kubernetes-rhdp", + "vault/gcp-secrets-engine": "/vault/tutorials/secrets-management/gcp-secrets-engine", + "vault/pki-unified-crl-ocsp-cross-cluster": "/vault/tutorials/secrets-management/pki-unified-crl-ocsp-cross-cluster", + "vault/namespaces-secrets-sharing": "/vault/tutorials/enterprise/namespaces-secrets-sharing", + "vault/agent-quick-start": "/vault/tutorials/vault-agent/agent-quick-start", + "nomad/sso-oidc-vault": "/nomad/tutorials/single-sign-on/sso-oidc-vault", + "boundary/hcp-certificate-injection": "/boundary/tutorials/credential-management/hcp-certificate-injection", + "boundary/hcp-manage-multi-hop": "/boundary/tutorials/hcp-administration/hcp-manage-multi-hop", + "boundary/oss-manage-workers": "/boundary/tutorials/oss-administration/oss-manage-workers", + "well-architected-framework/reliability-monitoring-service-to-service-communication-with-envoy": "/well-architected-framework/reliability/reliability-monitoring-service-to-service-communication-with-envoy", + "well-architected-framework/reliability-consul-monitoring-alerts": "/well-architected-framework/reliability/reliability-consul-monitoring-alerts", + "well-architected-framework/reliability-consul-monitoring-consul-components": "/well-architected-framework/reliability/reliability-consul-monitoring-consul-components", + "terraform/dynamic-credentials": "/terraform/tutorials/cloud/dynamic-credentials", + "onboarding/consul-terraform-starter-code-kubernetes": "/onboarding/consul-enterprise-week-1/consul-terraform-starter-code-kubernetes", + "onboarding/consul-terraform-starter-code-examples": "/onboarding/consul-enterprise-week-1/consul-terraform-starter-code-examples", + "onboarding/consul-grafana-dashboard": "/onboarding/consul-enterprise-week-3/consul-grafana-dashboard", + "onboarding/consul-enterprise-wan-fed-vms-kubernetes-mesh-gw": "/onboarding/consul-enterprise-week-5/consul-enterprise-wan-fed-vms-kubernetes-mesh-gw", + "onboarding/consul-enterprise-wan-fed-mesh-gateways-overview": "/onboarding/consul-enterprise-week-5/consul-enterprise-wan-fed-mesh-gateways-overview", + "onboarding/consul-enterprise-wan-fed-kubernetes-clusters-mesh-gw": "/onboarding/consul-enterprise-week-5/consul-enterprise-wan-fed-kubernetes-clusters-mesh-gw", + "onboarding/consul-enterprise-w6-welcome": "/onboarding/consul-enterprise-week-6/consul-enterprise-w6-welcome", + "onboarding/consul-enterprise-w5-wrap-up": "/onboarding/consul-enterprise-week-5/consul-enterprise-w5-wrap-up", + "onboarding/consul-enterprise-w4-welcome": "/onboarding/consul-enterprise-week-4/consul-enterprise-w4-welcome", + "onboarding/consul-enterprise-w5-welcome": "/onboarding/consul-enterprise-week-5/consul-enterprise-w5-welcome", + "onboarding/consul-enterprise-w3-welcome": "/onboarding/consul-enterprise-week-3/consul-enterprise-w3-welcome", + "onboarding/consul-enterprise-w3-wrap-up": "/onboarding/consul-enterprise-week-3/consul-enterprise-w3-wrap-up", + "onboarding/consul-enterprise-w2-welcome": "/onboarding/consul-enterprise-week-2/consul-enterprise-w2-welcome", + "onboarding/consul-enterprise-w2-wrap-up": "/onboarding/consul-enterprise-week-2/consul-enterprise-w2-wrap-up", + "onboarding/consul-enterprise-w1-wrap-up": "/onboarding/consul-enterprise-week-1/consul-enterprise-w1-wrap-up", + "onboarding/consul-enterprise-w1-welcome": "/onboarding/consul-enterprise-week-1/consul-enterprise-w1-welcome", + "onboarding/consul-enterprise-upgrading-specific-versions": "/onboarding/consul-enterprise-week-3/consul-enterprise-upgrading-specific-versions", + "onboarding/consul-enterprise-standard-upgrades": "/onboarding/consul-enterprise-week-3/consul-enterprise-standard-upgrades", + "onboarding/consul-enterprise-service-definitions": "/onboarding/consul-enterprise-week-2/consul-enterprise-service-definitions", + "onboarding/consul-enterprise-snapshot-restore": "/onboarding/consul-enterprise-week-3/consul-enterprise-snapshot-restore", + "onboarding/consul-enterprise-query-services-dns": "/onboarding/consul-enterprise-week-2/consul-enterprise-query-services-dns", + "onboarding/consul-enterprise-onboarding-slides": "/onboarding/consul-enterprise-week-1/consul-enterprise-onboarding-slides", + "onboarding/consul-enterprise-key-metrics": "/onboarding/consul-enterprise-week-3/consul-enterprise-key-metrics", + "onboarding/consul-enterprise-network-segments-overview": "/onboarding/consul-enterprise-week-5/consul-enterprise-network-segments-overview", + "onboarding/consul-enterprise-health-checks": "/onboarding/consul-enterprise-week-2/consul-enterprise-health-checks", + "onboarding/consul-enterprise-helm-chart": "/onboarding/consul-enterprise-week-1/consul-enterprise-helm-chart", + "onboarding/consul-enterprise-enterprise-license": "/onboarding/consul-enterprise-week-3/consul-enterprise-enterprise-license", + "onboarding/consul-enterprise-agents-overview": "/onboarding/consul-enterprise-week-2/consul-enterprise-agents-overview", + "onboarding/consul-enterprise-4-pillars-service-networking": "/onboarding/consul-enterprise-week-1/consul-enterprise-4-pillars-service-networking", + "packer/revoke-image": "/packer/tutorials/hcp/revoke-image", + "terraform/projects": "/terraform/tutorials/cloud/projects", + "vault/disaster-recovery-replication-failover": "/vault/tutorials/enterprise/disaster-recovery-replication-failover", + "terraform/tfe-metrics": "/terraform/tutorials/enterprise/tfe-metrics", + "nomad/cluster-setup-overview": "/nomad/tutorials/cluster-setup/cluster-setup-overview", + "nomad/cluster-setup-gcp": "/nomad/tutorials/cluster-setup/cluster-setup-gcp", + "nomad/cluster-setup-azure": "/nomad/tutorials/cluster-setup/cluster-setup-azure", + "well-architected-framework/reliability-consul-capacity-planning": "/well-architected-framework/reliability/reliability-consul-capacity-planning", + "consul/virtual-machine-gs-monitoring": "/consul/tutorials/get-started-vms/virtual-machine-gs-monitoring", + "onboarding/vault-enterprise-w6pt3-wrap-up": "/onboarding/vault-enterprise-week-6pt3/vault-enterprise-w6pt3-wrap-up", + "onboarding/vault-enterprise-w6pt3-welcome": "/onboarding/vault-enterprise-week-6pt3/vault-enterprise-w6pt3-welcome", + "onboarding/vault-enterprise-w6pt2-wrap-up": "/onboarding/vault-enterprise-week-6pt2/vault-enterprise-w6pt2-wrap-up", + "onboarding/vault-enterprise-w6pt2-welcome": "/onboarding/vault-enterprise-week-6pt2/vault-enterprise-w6pt2-welcome", + "onboarding/vault-enterprise-w6pt1-wrap-up": "/onboarding/vault-enterprise-week-6pt1/vault-enterprise-w6pt1-wrap-up", + "onboarding/vault-enterprise-w6pt1-welcome": "/onboarding/vault-enterprise-week-6pt1/vault-enterprise-w6pt1-welcome", + "vault/database-secrets-mssql": "/vault/tutorials/db-credentials/database-secrets-mssql", + "vault/database-secrets-mssql-aws-rds": "/vault/tutorials/db-credentials/database-secrets-mssql-aws-rds", + "vault/intro-vault-aws-lambda-extension": "/vault/tutorials/app-integration/intro-vault-aws-lambda-extension", + "packer/multicloud": "/packer/tutorials/cloud-production/multicloud", + "vault/kubernetes-minikube-tls": "/vault/tutorials/kubernetes/kubernetes-minikube-tls", + "nomad/variables-tasks": "/nomad/tutorials/variables/variables-tasks", + "nomad/variables-create": "/nomad/tutorials/variables/variables-create", + "nomad/variables-acls": "/nomad/tutorials/variables/variables-acls", + "cloud/hcp-vault-namespace-considerations": "/vault/tutorials/cloud-ops/hcp-vault-namespace-considerations", + "vault/audit-elastic-incident-response": "/vault/tutorials/operations/audit-elastic-incident-response", + "well-architected-framework/security-zero-trust-video": "/well-architected-framework/security/security-zero-trust-video", + "well-architected-framework/security-sensitive-data": "/well-architected-framework/security/security-sensitive-data", + "well-architected-framework/security-prevent-lateral-movement": "/well-architected-framework/security/security-prevent-lateral-movement", + "well-architected-framework/security-cicd-vault": "/well-architected-framework/security/security-cicd-vault", + "well-architected-framework/security-introduction": "/well-architected-framework/security/security-introduction", + "terraform/confluent-provider": "/terraform/tutorials/applications/confluent-provider", + "cloud/gcp-connect-hcp": "/hcp/tutorials/networking/gcp-connect-hcp", + "terraform/no-code-provisioning": "/terraform/tutorials/cloud/no-code-provisioning", + "terraform/drift-and-opa": "/terraform/tutorials/cloud/drift-and-opa", + "boundary/hcp-private-vault-cred-injection": "/boundary/tutorials/credential-management/hcp-private-vault-cred-injection", + "terraform/associate-study-003": "/terraform/tutorials/certification-003/associate-study-003", + "terraform/associate-review-003": "/terraform/tutorials/certification-003/associate-review-003", + "boundary/hcp-vault-cred-brokering-quickstart": "/boundary/tutorials/credential-management/hcp-vault-cred-brokering-quickstart", + "consul/virtual-machine-gs-service-mesh": "/consul/tutorials/get-started-vms/virtual-machine-gs-service-mesh", + "consul/virtual-machine-gs-deploy": "/consul/tutorials/get-started-vms/virtual-machine-gs-deploy", + "terraform/providers-plugin-framework-resource-update": "/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-resource-update", + "terraform/providers-plugin-framework-resource-import": "/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-resource-import", + "terraform/providers-plugin-framework-resource-delete": "/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-resource-delete", + "terraform/providers-plugin-framework-release-publish": "/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-release-publish", + "terraform/providers-plugin-framework-provider": "/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-provider", + "terraform/providers-plugin-framework-provider-configure": "/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-provider-configure", + "terraform/providers-plugin-framework-documentation-generation": "/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-documentation-generation", + "terraform/providers-plugin-framework-logging": "/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-logging", + "terraform/providers-plugin-framework-data-source-read": "/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-data-source-read", + "terraform/providers-plugin-framework-acceptance-testing": "/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-acceptance-testing", + "consul/cluster-peering": "/consul/tutorials/developer-mesh/cluster-peering", + "consul/consul-windows-workloads": "/consul/tutorials/developer-mesh/consul-windows-workloads", + "consul/hcp-gs-deploy": "/consul/tutorials/get-started-hcp/hcp-gs-deploy", + "consul/hcp-gs-canary-deployments": "/consul/tutorials/get-started-hcp/hcp-gs-canary-deployments", + "consul/kubernetes-gs-service-mesh": "/consul/tutorials/get-started-kubernetes/kubernetes-gs-service-mesh", + "consul/kubernetes-gs-observability": "/consul/tutorials/get-started-kubernetes/kubernetes-gs-observability", + "consul/kubernetes-gs-ingress": "/consul/tutorials/get-started-kubernetes/kubernetes-gs-ingress", + "consul/kubernetes-gs-deploy": "/consul/tutorials/get-started-kubernetes/kubernetes-gs-deploy", + "consul/hcp-gs-intentions": "/consul/tutorials/get-started-hcp/hcp-gs-intentions", + "packer/github-actions": "/packer/tutorials/cloud-production/github-actions", + "terraform/module-object-attributes": "/terraform/tutorials/modules/module-object-attributes", + "consul/vault-pki-consul-connect-ca": "/consul/tutorials/vault-secure/vault-pki-consul-connect-ca", + "nomad/keyboard-commands": "/nomad/tutorials/web-ui/keyboard-commands", + "boundary/hcp-getting-started-credentials": "/boundary/tutorials/hcp-getting-started/hcp-getting-started-credentials", + "well-architected-framework/operational-excellence-tao": "/well-architected-framework/operational-excellence/operational-excellence-tao", + "vault/kubernetes-secrets-engine": "/vault/tutorials/kubernetes/kubernetes-secrets-engine", + "nomad/service-discovery-consul-conversion": "/nomad/tutorials/service-discovery/service-discovery-consul-conversion", + "nomad/service-discovery-app-deployment": "/nomad/tutorials/service-discovery/service-discovery-app-deployment", + "nomad/cluster-setup-aws": "/nomad/tutorials/cluster-setup/cluster-setup-aws", + "boundary/hcp-ssh-cred-injection": "/boundary/tutorials/hcp-administration/hcp-ssh-cred-injection", + "vault/monitoring-vault-with-datadog": "/vault/tutorials/monitoring/monitoring-vault-with-datadog", + "consul/serverless-consul-with-lambda": "/consul/tutorials/cloud-integrations/serverless-consul-with-lambda", + "terraform/aws-dynamodb-scale": "/terraform/tutorials/aws/aws-dynamodb-scale", + "cloud/consul-end-to-end-aks": "/consul/tutorials/cloud-deploy-automation/consul-end-to-end-aks", + "cloud/vault-access-cluster": "/vault/tutorials/cloud/vault-access-cluster", + "cloud/vault-eks-jwt-auth": "/vault/tutorials/cloud-ops/vault-eks-jwt-auth", + "vault/compare-kv-versions": "/vault/tutorials/secrets-management/compare-kv-versions", + "terraform/enterprise-reference-architecture": "/well-architected-framework/terraform/enterprise-reference-architecture", + "boundary/hcp-manage-workers": "/boundary/tutorials/hcp-administration/hcp-manage-workers", + "boundary/hcp-manage-users-groups": "/boundary/tutorials/hcp-administration/hcp-manage-users-groups", + "boundary/hcp-manage-sessions": "/boundary/tutorials/hcp-administration/hcp-manage-sessions", + "boundary/hcp-manage-targets": "/boundary/tutorials/hcp-administration/hcp-manage-targets", + "boundary/hcp-manage-scopes": "/boundary/tutorials/hcp-administration/hcp-manage-scopes", + "boundary/hcp-manage-roles": "/boundary/tutorials/hcp-administration/hcp-manage-roles", + "boundary/hcp-getting-started-intro": "/boundary/tutorials/hcp-getting-started/hcp-getting-started-intro", + "boundary/hcp-manage-intro": "/boundary/tutorials/hcp-administration/hcp-manage-intro", + "boundary/hcp-getting-started-desktop-app": "/boundary/tutorials/hcp-getting-started/hcp-getting-started-desktop-app", + "boundary/hcp-getting-started-install": "/boundary/tutorials/hcp-getting-started/hcp-getting-started-install", + "boundary/hcp-getting-started-console": "/boundary/tutorials/hcp-getting-started/hcp-getting-started-console", + "boundary/hcp-getting-started-create": "/boundary/tutorials/hcp-getting-started/hcp-getting-started-create", + "boundary/hcp-getting-started-connect": "/boundary/tutorials/hcp-getting-started/hcp-getting-started-connect", + "boundary/hcp-getting-started-cloud": "/boundary/tutorials/hcp-getting-started/hcp-getting-started-cloud", + "vault/raft-upgrade-automation": "/vault/tutorials/raft/raft-upgrade-automation", + "vault/raft-redundancy-zones": "/vault/tutorials/raft/raft-redundancy-zones", + "terraform/validation-enforcement": "/terraform/tutorials/cloud/validation-enforcement", + "vault/manage-codified-vault-hcp-terraform": "/vault/tutorials/cloud-ops/manage-codified-vault-hcp-terraform", + "vault/apply-codified-vault-hcp-terraform": "/vault/tutorials/operations/apply-codified-vault-hcp-terraform", + "consul/kubernetes-vault-consul-secrets-management": "/consul/tutorials/kubernetes/kubernetes-vault-consul-secrets-management", + "vault/kubernetes-minikube-raft": "/vault/tutorials/kubernetes/kubernetes-minikube-raft", + "cloud/consul-end-to-end-azure-vm": "/consul/tutorials/cloud-deploy-automation/consul-end-to-end-azure-vm", + "cloud/consul-client-azure-virtual-machines": "/hcp/tutorials/consul-cloud/consul-client-azure-virtual-machines", + "cloud/consul-client-aws-ec2": "/hcp/tutorials/consul-cloud/consul-client-aws-ec2", + "cloud/consul-client-aks": "/hcp/tutorials/consul-cloud/consul-client-aks", + "cloud/azure-peering-hcp": "/hcp/tutorials/networking/azure-peering-hcp", + "well-architected-framework/reliability-tolerate-failure": "/well-architected-framework/reliability/reliability-tolerate-failure", + "well-architected-framework/reliability-introduction": "/well-architected-framework/reliability/reliability-introduction", + "well-architected-framework/reliability-implementation-resources": "/well-architected-framework/reliability/reliability-implementation-resources", + "boundary/event-logging": "/boundary/tutorials/self-managed-deployment/event-logging", + "terraform/custom-conditions": "/terraform/tutorials/configuration-language/custom-conditions", + "vault/active-directory-mfa-login-totp": "/vault/tutorials/auth-methods/active-directory-mfa-login-totp", + "nomad/schedule-edge-services": "/nomad/tutorials/edge/schedule-edge-services", + "onboarding/terraform-enterprise-w9-welcome": "/onboarding/terraform-enterprise-week-9/terraform-enterprise-w9-welcome", + "onboarding/terraform-enterprise-workspaces": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-workspaces", + "onboarding/terraform-enterprise-w8-welcome": "/onboarding/terraform-enterprise-week-8/terraform-enterprise-w8-welcome", + "onboarding/terraform-enterprise-w8-wrap-up": "/onboarding/terraform-enterprise-week-8/terraform-enterprise-w8-wrap-up", + "onboarding/terraform-enterprise-w7-welcome": "/onboarding/terraform-enterprise-week-7/terraform-enterprise-w7-welcome", + "onboarding/terraform-enterprise-w7-wrap-up": "/onboarding/terraform-enterprise-week-7/terraform-enterprise-w7-wrap-up", + "onboarding/terraform-enterprise-w6-welcome": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-w6-welcome", + "onboarding/terraform-enterprise-w6-wrap-up": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-w6-wrap-up", + "onboarding/terraform-enterprise-w5-welcome": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-w5-welcome", + "onboarding/terraform-enterprise-w5-wrap-up": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-w5-wrap-up", + "onboarding/terraform-enterprise-w4-welcome": "/onboarding/terraform-enterprise-week-4/terraform-enterprise-w4-welcome", + "onboarding/terraform-enterprise-upgrading": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-upgrading", + "onboarding/terraform-enterprise-ui-and-vcs-workflow": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-ui-and-vcs-workflow", + "onboarding/terraform-enterprise-upgrading-patching-node-instances": "/onboarding/terraform-enterprise-week-7/terraform-enterprise-upgrading-patching-node-instances", + "onboarding/terraform-enterprise-tfe-metrics": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-tfe-metrics", + "onboarding/terraform-enterprise-release-notes": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-release-notes", + "onboarding/terraform-enterprise-sentinel-policies-library": "/onboarding/terraform-enterprise-week-8/terraform-enterprise-sentinel-policies-library", + "onboarding/terraform-enterprise-registry-publishing": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-registry-publishing", + "onboarding/terraform-enterprise-publishing-tfc-private-registry": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-publishing-tfc-private-registry", + "onboarding/terraform-enterprise-providers": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-providers", + "onboarding/terraform-enterprise-monitoring": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-monitoring", + "onboarding/terraform-enterprise-manage-credentials": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-manage-credentials", + "onboarding/terraform-enterprise-log-forwarding": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-log-forwarding", + "onboarding/terraform-enterprise-export-tfe-configuration": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-export-tfe-configuration", + "onboarding/terraform-enterprise-health-check-endpoint": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-health-check-endpoint", + "onboarding/terraform-enterprise-database-maintenance": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-database-maintenance", + "onboarding/terraform-enterprise-cli-workflow": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-cli-workflow", + "onboarding/terraform-enterprise-client-libs-and-tools": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-client-libs-and-tools", + "onboarding/terraform-enterprise-availability-during-upgrades": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-availability-during-upgrades", + "onboarding/terraform-enterprise-backup-restore": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-backup-restore", + "onboarding/terraform-enterprise-auto-start-runs": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-auto-start-runs", + "onboarding/terraform-enterprise-audit-logs": "/onboarding/terraform-enterprise-week-5/terraform-enterprise-audit-logs", + "onboarding/terraform-enterprise-api-workflow": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-api-workflow", + "onboarding/terraform-enterprise-admin-cli-commands": "/onboarding/terraform-enterprise-week-7/terraform-enterprise-admin-cli-commands", + "onboarding/terraform-enterprise-add-public-providers-modules-to-private-registry": "/onboarding/terraform-enterprise-week-6/terraform-enterprise-add-public-providers-modules-to-private-registry", + "onboarding/terraform-enterprise-active-active": "/onboarding/terraform-enterprise-week-7/terraform-enterprise-active-active", + "boundary/prometheus-metrics": "/boundary/tutorials/self-managed-deployment/prometheus-metrics", + "terraform/cloud-run-tasks-snyk": "/terraform/tutorials/cloud/cloud-run-tasks-snyk", + "vault/managed-key-pki": "/vault/tutorials/secrets-management/managed-key-pki", + "consul/amazon-ecs-admin-partitions": "/consul/tutorials/enterprise/amazon-ecs-admin-partitions", + "cloud/vault-replication-terraform": "/vault/tutorials/cloud-ops/vault-replication-terraform", + "packer/hcp-schedule-image-iterations-revocation": "/packer/tutorials/hcp/hcp-schedule-image-iterations-revocation", + "vault/aws-eks-anywhere": "/vault/tutorials/kubernetes/aws-eks-anywhere", + "cloud/vault-oidc-okta": "/vault/tutorials/cloud-ops/vault-oidc-okta", + "terraform/cloud-vcs-change": "/terraform/tutorials/cloud-get-started/cloud-vcs-change", + "boundary/azure-sql-database": "/boundary/tutorials/self-managed-deployment/azure-sql-database", + "well-architected-framework/operational-excellence-resources": "/well-architected-framework/operational-excellence/operational-excellence-resources", + "well-architected-framework/operational-excellence-enable-teams": "/well-architected-framework/operational-excellence/operational-excellence-enable-teams", + "well-architected-framework/operational-excellence-introduction": "/well-architected-framework/operational-excellence/operational-excellence-introduction", + "well-architected-framework/operational-excellence-automate-infrastructure": "/well-architected-framework/operational-excellence/operational-excellence-automate-infrastructure", + "well-architected-framework/implement-cloud-operating-model": "/well-architected-framework/com/implement-cloud-operating-model", + "well-architected-framework/cloud-operating-model": "/well-architected-framework/com/cloud-operating-model", + "vault/multi-factor-authentication": "/vault/tutorials/auth-methods/multi-factor-authentication", + "vault/mount-move": "/vault/tutorials/enterprise/mount-move", + "vault/ibm-db2-openldap": "/vault/tutorials/secrets-management/ibm-db2-openldap", + "terraform/plan": "/terraform/tutorials/cli/plan", + "terraform/init": "/terraform/tutorials/cli/init", + "terraform/apply": "/terraform/tutorials/cli/apply", + "nomad/external-application-load-balancing": "/nomad/tutorials/load-balancing/external-application-load-balancing", + "packer/setup-tfc-run-task": "/packer/tutorials/hcp/setup-tfc-run-task", + "packer/run-tasks-resource-image-validation": "/packer/tutorials/hcp/run-tasks-resource-image-validation", + "packer/run-tasks-data-source-image-validation": "/packer/tutorials/hcp/run-tasks-data-source-image-validation", + "packer/hcp-immediately-revoke-artifact-versions": "/packer/tutorials/hcp-get-started/hcp-immediately-revoke-artifact-versions", + "nomad/hcdiag-with-nomad": "/nomad/tutorials/manage-clusters/hcdiag-with-nomad", + "cloud/vault-auth-method-aws": "/vault/tutorials/cloud-ops/vault-auth-method-aws", + "cloud/vault-replication": "/vault/tutorials/cloud-ops/vault-replication", + "boundary/aws-host-catalogs": "/boundary/tutorials/host-management/aws-host-catalogs", + "terraform/preview-environments-vercel": "/terraform/tutorials/applications/preview-environments-vercel", + "onboarding/terraform-enterprise-w3-wrap-up": "/onboarding/terraform-enterprise-week-3/terraform-enterprise-w3-wrap-up", + "onboarding/terraform-enterprise-w3-welcome": "/onboarding/terraform-enterprise-week-3/terraform-enterprise-w3-welcome", + "onboarding/terraform-enterprise-w2-wrap-up": "/onboarding/terraform-enterprise-week-2/terraform-enterprise-w2-wrap-up", + "onboarding/terraform-enterprise-w2-welcome": "/onboarding/terraform-enterprise-week-2/terraform-enterprise-w2-welcome", + "onboarding/terraform-enterprise-w1-wrap-up": "/onboarding/terraform-enterprise-week-1/terraform-enterprise-w1-wrap-up", + "onboarding/terraform-enterprise-w1-welcome": "/onboarding/terraform-enterprise-week-1/terraform-enterprise-w1-welcome", + "onboarding/terraform-enterprise-state": "/onboarding/terraform-enterprise-week-3/terraform-enterprise-state", + "onboarding/terraform-enterprise-reference-architecture": "/onboarding/terraform-enterprise-week-1/terraform-enterprise-reference-architecture", + "onboarding/terraform-enterprise-preinstall-checklist": "/onboarding/terraform-enterprise-week-1/terraform-enterprise-preinstall-checklist", + "onboarding/terraform-enterprise-operational-modes": "/onboarding/terraform-enterprise-week-2/terraform-enterprise-operational-modes", + "onboarding/terraform-enterprise-network-access": "/onboarding/terraform-enterprise-week-1/terraform-enterprise-network-access", + "onboarding/terraform-enterprise-interactive-install": "/onboarding/terraform-enterprise-week-2/terraform-enterprise-interactive-install", + "onboarding/terraform-enterprise-importing": "/onboarding/terraform-enterprise-week-3/terraform-enterprise-importing", + "onboarding/terraform-enterprise-data-storage-reqs": "/onboarding/terraform-enterprise-week-1/terraform-enterprise-data-storage-reqs", + "onboarding/terraform-enterprise-architecture": "/onboarding/terraform-enterprise-week-2/terraform-enterprise-architecture", + "onboarding/terraform-enterprise-airgapped-environments": "/onboarding/terraform-enterprise-week-2/terraform-enterprise-airgapped-environments", + "terraform/aws-asg": "/terraform/tutorials/aws/aws-asg", + "consul/kubernetes-admin-partitions": "/consul/tutorials/kubernetes/kubernetes-admin-partitions", + "vault/agent-aws-ecs": "/vault/tutorials/vault-agent/agent-aws-ecs", + "consul/kubernetes-api-gateway": "/consul/tutorials/kubernetes/kubernetes-api-gateway", + "cloud/consul-end-to-end-resources": "/consul/tutorials/cloud-deploy-automation/consul-end-to-end-resources", + "cloud/consul-end-to-end-overview": "/consul/tutorials/cloud-deploy-automation/consul-end-to-end-overview", + "cloud/consul-end-to-end-existing-eks": "/consul/tutorials/cloud-deploy-automation/consul-end-to-end-existing-eks", + "cloud/consul-end-to-end-eks": "/consul/tutorials/cloud-deploy-automation/consul-end-to-end-eks", + "cloud/consul-end-to-end-ecs": "/consul/tutorials/cloud-deploy-automation/consul-end-to-end-ecs", + "cloud/consul-end-to-end-ec2": "/consul/tutorials/cloud-deploy-automation/consul-end-to-end-ec2", + "terraform/pattern-recovery": "/terraform/tutorials/recommended-patterns/pattern-recovery", + "consul/introduction-chaos-engineering": "/consul/tutorials/resiliency/introduction-chaos-engineering", + "vault/transform-code-example": "/vault/tutorials/adp/transform-code-example", + "consul/hcdiag-with-consul": "/consul/tutorials/datacenter-operations/hcdiag-with-consul", + "onboarding/vault-enterprise-w7-wrap-up": "/onboarding/vault-enterprise-week-7/vault-enterprise-w7-wrap-up", + "onboarding/vault-enterprise-w7-welcome": "/onboarding/vault-enterprise-week-7/vault-enterprise-w7-welcome", + "onboarding/vault-enterprise-w5-wrap-up": "/onboarding/vault-enterprise-week-5/vault-enterprise-w5-wrap-up", + "onboarding/vault-enterprise-w5-welcome": "/onboarding/vault-enterprise-week-5/vault-enterprise-w5-welcome", + "onboarding/vault-enterprise-migrating-from-oss": "/onboarding/vault-enterprise-week-2/vault-enterprise-migrating-from-oss", + "onboarding/vault-enterprise-instruqt-vault-and-google": "/onboarding/vault-enterprise-week-2/vault-enterprise-instruqt-vault-and-google", + "onboarding/vault-enterprise-instruqt-pki-backend": "/onboarding/vault-enterprise-week-5/vault-enterprise-instruqt-pki-backend", + "onboarding/vault-enterprise-instruqt-aws-dynamic-secrets": "/onboarding/vault-enterprise-week-2/vault-enterprise-instruqt-aws-dynamic-secrets", + "onboarding/vault-enterprise-app-integration": "/onboarding/vault-enterprise-week-6pt1/vault-enterprise-app-integration", + "onboarding/vault-enterprise-w4-wrap-up": "/onboarding/vault-enterprise-week-4/vault-enterprise-w4-wrap-up", + "onboarding/vault-enterprise-w4-welcome": "/onboarding/vault-enterprise-week-4/vault-enterprise-w4-welcome", + "onboarding/vault-enterprise-w3-wrap-up": "/onboarding/vault-enterprise-week-3/vault-enterprise-w3-wrap-up", + "onboarding/vault-enterprise-w3-welcome": "/onboarding/vault-enterprise-week-3/vault-enterprise-w3-welcome", + "onboarding/vault-enterprise-w2-wrap-up": "/onboarding/vault-enterprise-week-2/vault-enterprise-w2-wrap-up", + "onboarding/vault-enterprise-w2-welcome": "/onboarding/vault-enterprise-week-2/vault-enterprise-w2-welcome", + "onboarding/vault-enterprise-w1-wrap-up": "/onboarding/vault-enterprise-week-1/vault-enterprise-w1-wrap-up", + "onboarding/vault-enterprise-w1-welcome": "/onboarding/vault-enterprise-week-1/vault-enterprise-w1-welcome", + "onboarding/vault-enterprise-vault-security-model": "/onboarding/vault-enterprise-week-1/vault-enterprise-vault-security-model", + "onboarding/vault-enterprise-starter-modules": "/onboarding/vault-enterprise-week-2/vault-enterprise-starter-modules", + "onboarding/vault-enterprise-seal-unseal": "/onboarding/vault-enterprise-week-1/vault-enterprise-seal-unseal", + "onboarding/vault-enterprise-okta-oidc": "/onboarding/vault-enterprise-week-3/vault-enterprise-okta-oidc", + "onboarding/vault-enterprise-mfa": "/onboarding/vault-enterprise-week-3/vault-enterprise-mfa", + "onboarding/vault-enterprise-integrated-storage": "/onboarding/vault-enterprise-week-1/vault-enterprise-integrated-storage", + "onboarding/vault-enterprise-instruqt-value-vault-dr": "/onboarding/vault-enterprise-week-3/vault-enterprise-instruqt-value-vault-dr", + "onboarding/vault-enterprise-instruqt-ssh-secrets-engine": "/onboarding/vault-enterprise-week-3/vault-enterprise-instruqt-ssh-secrets-engine", + "onboarding/vault-enterprise-instruqt-db-credentials": "/onboarding/vault-enterprise-week-3/vault-enterprise-instruqt-db-credentials", + "onboarding/vault-enterprise-instruqt-aws-auth-method": "/onboarding/vault-enterprise-week-3/vault-enterprise-instruqt-aws-auth-method", + "onboarding/vault-enterprise-ilt": "/onboarding/vault-enterprise-week-2/vault-enterprise-ilt", + "onboarding/vault-enterprise-architecture": "/onboarding/vault-enterprise-week-1/vault-enterprise-architecture", + "terraform/move-config": "/terraform/tutorials/configuration-language/move-config", + "terraform/migrate-remote-s3-backend-tfc": "/terraform/tutorials/cloud/migrate-remote-s3-backend-tfc", + "terraform/aws-control-tower-aft": "/terraform/tutorials/aws/aws-control-tower-aft", + "consul/security": "/consul/tutorials/production-deploy/security", + "consul/multi-security": "/consul/tutorials/multi-cluster-deploy/multi-security", + "consul/multi-disaster-recovery": "/consul/tutorials/multi-cluster-deploy/multi-disaster-recovery", + "consul/multi-cluster-reference-architecture": "/consul/tutorials/multi-cluster-deploy/multi-cluster-reference-architecture", + "consul/disaster-recovery": "/consul/tutorials/production-deploy/disaster-recovery", + "terraform/enable-sso-saml-tfe-okta": "/terraform/tutorials/enterprise/enable-sso-saml-tfe-okta", + "boundary/azure-host-catalogs": "/boundary/tutorials/host-management/azure-host-catalogs", + "vault/oidc-identity-provider": "/vault/tutorials/auth-methods/oidc-identity-provider", + "vault/key-management-secrets-engine-gcp-cloud-kms": "/vault/tutorials/adp/key-management-secrets-engine-gcp-cloud-kms", + "vault/emergency-break-glass-features": "/vault/tutorials/operations/emergency-break-glass-features", + "vault/customize-http-headers": "/vault/tutorials/operations/customize-http-headers", + "terraform/cloud-create-variable-set": "/terraform/tutorials/cloud-get-started/cloud-create-variable-set", + "terraform/cloud-multiple-variable-sets": "/terraform/tutorials/cloud/cloud-multiple-variable-sets", + "consul/consul-terraform-sync-pan": "/consul/tutorials/network-infrastructure-automation/consul-terraform-sync-pan", + "onboarding/tfc-w7-wrap-up": "/onboarding/tfc-week-7/tfc-w7-wrap-up", + "onboarding/tfc-w7-welcome": "/onboarding/tfc-week-7/tfc-w7-welcome", + "onboarding/tfc-w7-sso": "/onboarding/tfc-week-7/tfc-w7-sso", + "onboarding/tfc-w7-manage-workspace-access": "/onboarding/tfc-week-7/tfc-w7-manage-workspace-access", + "onboarding/tfc-w7-audit-logging": "/onboarding/tfc-week-7/tfc-w7-audit-logging", + "onboarding/tfc-w7-app-and-audit-logs": "/onboarding/tfc-week-7/tfc-w7-app-and-audit-logs", + "onboarding/tfc-w7-2fa": "/onboarding/tfc-week-7/tfc-w7-2fa", + "onboarding/tfc-w6-wrap-up": "/onboarding/tfc-week-6/tfc-w6-wrap-up", + "onboarding/tfc-w6-welcome": "/onboarding/tfc-week-6/tfc-w6-welcome", + "onboarding/tfc-w6-benefits-vcs": "/onboarding/tfc-week-6/tfc-w6-benefits-vcs", + "onboarding/vault-metrics-splunk": "/onboarding/hcp-vault-week-7/vault-metrics-splunk", + "onboarding/vault-metrics-guide": "/onboarding/hcp-vault-week-7/vault-metrics-guide", + "onboarding/vault-metrics-grafana": "/onboarding/hcp-vault-week-7/vault-metrics-grafana", + "onboarding/vault-metrics-datadog": "/onboarding/hcp-vault-week-7/vault-metrics-datadog", + "onboarding/vault-audit-log-splunk": "/onboarding/hcp-vault-week-7/vault-audit-log-splunk", + "onboarding/vault-audit-log-grafana": "/onboarding/hcp-vault-week-7/vault-audit-log-grafana", + "onboarding/vault-audit-log-datadog": "/onboarding/hcp-vault-week-7/vault-audit-log-datadog", + "consul/consul-admin-partitions": "/consul/tutorials/enterprise/consul-admin-partitions", + "terraform/console": "/terraform/tutorials/cli/console", + "vault/monitor-telemetry-grafana-prometheus": "/vault/tutorials/monitoring/monitor-telemetry-grafana-prometheus", + "terraform/cdktf-applications": "/terraform/tutorials/cdktf/cdktf-applications", + "packer/hcp-push-artifact-metadata": "/packer/tutorials/hcp-get-started/hcp-push-artifact-metadata", + "packer/hcp-artifact-channels": "/packer/tutorials/hcp-get-started/hcp-artifact-channels", + "packer/hcp-create-child-artifact": "/packer/tutorials/hcp-get-started/hcp-create-child-artifact", + "packer/golden-image-with-hcp-packer": "/packer/tutorials/cloud-production/golden-image-with-hcp-packer", + "vault/agent-read-secrets": "/vault/tutorials/vault-agent/agent-read-secrets", + "terraform/microsoft-caf-enterprise-scale": "/terraform/tutorials/azure/microsoft-caf-enterprise-scale", + "nomad/nomad-pack-writing-packs": "/nomad/tutorials/nomad-pack/nomad-pack-writing-packs", + "nomad/nomad-pack-intro": "/nomad/tutorials/nomad-pack/nomad-pack-intro", + "onboarding/tfc-w5-wrap-up": "/onboarding/tfc-week-5/tfc-w5-wrap-up", + "onboarding/tfc-w5-workspaces": "/onboarding/tfc-week-5/tfc-w5-workspaces", + "onboarding/tfc-w5-welcome": "/onboarding/tfc-week-5/tfc-w5-welcome", + "onboarding/tfc-w5-runs": "/onboarding/tfc-week-5/tfc-w5-runs", + "onboarding/tfc-w4-wrap-up": "/onboarding/tfc-week-4/tfc-w4-wrap-up", + "onboarding/tfc-w4-welcome": "/onboarding/tfc-week-4/tfc-w4-welcome", + "onboarding/tfc-w3-wrap-up": "/onboarding/tfc-week-3/tfc-w3-wrap-up", + "onboarding/tfc-w3-welcome": "/onboarding/tfc-week-3/tfc-w3-welcome", + "onboarding/tfc-w2-wrap-up": "/onboarding/tfc-week-2/tfc-w2-wrap-up", + "onboarding/tfc-w2-welcome": "/onboarding/tfc-week-2/tfc-w2-welcome", + "onboarding/tfc-w2-overview": "/onboarding/tfc-week-2/tfc-w2-overview", + "onboarding/tfc-w1-wrap-up": "/onboarding/tfc-week-1/tfc-w1-wrap-up", + "onboarding/tfc-w1-welcome": "/onboarding/tfc-week-1/tfc-w1-welcome", + "consul/service-mesh-gateway-ambassador": "/consul/tutorials/developer-mesh/service-mesh-gateway-ambassador", + "terraform/aws-cloud-control": "/terraform/tutorials/aws/aws-cloud-control", + "vault/pki-engine-external-ca": "/vault/tutorials/secrets-management/pki-engine-external-ca", + "vault/namespace-structure": "/vault/tutorials/enterprise/namespace-structure", + "cloud/vault-codify-mgmt": "/vault/tutorials/cloud-ops/vault-codify-mgmt", + "terraform/hcdiag-with-tfe": "/terraform/tutorials/enterprise/hcdiag-with-tfe", + "consul/consul-terraform-sync-f5-bigip-fast": "/consul/tutorials/network-infrastructure-automation/consul-terraform-sync-f5-bigip-fast", + "vault/ops-pro-study": "/vault/tutorials/ops-pro-cert/ops-pro-study", + "vault/ops-pro-review": "/vault/tutorials/ops-pro-cert/ops-pro-review", + "vault/ops-pro-overview": "/vault/tutorials/ops-pro-cert/ops-pro-overview", + "onboarding/hcp-vault-w9-wrap-up": "/onboarding/hcp-vault-week-9/hcp-vault-w9-wrap-up", + "onboarding/hcp-vault-w9-welcome": "/onboarding/hcp-vault-week-9/hcp-vault-w9-welcome", + "onboarding/hcp-vault-w9-migration": "/onboarding/hcp-vault-week-9/hcp-vault-w9-migration", + "onboarding/hcp-vault-w8-wrap-up": "/onboarding/hcp-vault-week-8/hcp-vault-w8-wrap-up", + "onboarding/hcp-vault-w8-welcome": "/onboarding/hcp-vault-week-8/hcp-vault-w8-welcome", + "onboarding/hcp-vault-w8-kubernetes": "/onboarding/hcp-vault-week-8/hcp-vault-w8-kubernetes", + "onboarding/hcp-vault-w8-ci-cd": "/onboarding/hcp-vault-week-8/hcp-vault-w8-ci-cd", + "onboarding/hcp-vault-w7-wrap-up": "/onboarding/hcp-vault-week-7/hcp-vault-w7-wrap-up", + "onboarding/hcp-vault-w7-welcome": "/onboarding/hcp-vault-week-7/hcp-vault-w7-welcome", + "onboarding/hcp-vault-w6-wrap-up": "/onboarding/hcp-vault-week-6/hcp-vault-w6-wrap-up", + "onboarding/hcp-vault-w6-welcome": "/onboarding/hcp-vault-week-6/hcp-vault-w6-welcome", + "onboarding/hcp-vault-w5-wrap-up": "/onboarding/hcp-vault-week-5/hcp-vault-w5-wrap-up", + "onboarding/hcp-vault-w5-welcome": "/onboarding/hcp-vault-week-5/hcp-vault-w5-welcome", + "onboarding/hcp-vault-w5-associate-cert": "/onboarding/hcp-vault-week-5/hcp-vault-w5-associate-cert", + "onboarding/hcp-vault-w10-welcome": "/onboarding/hcp-vault-week-10/hcp-vault-w10-welcome", + "terraform/tfe-log-forwarding": "/terraform/tutorials/enterprise/tfe-log-forwarding", + "terraform/github-user-teams": "/terraform/tutorials/it-saas/github-user-teams", + "terraform/rds-upgrade": "/terraform/tutorials/aws/rds-upgrade", + "consul/docker-compose-auto-config": "/consul/tutorials/security-operations/docker-compose-auto-config", + "consul/consul-terraform-sync-terraform-enterprise": "/consul/tutorials/network-infrastructure-automation/consul-terraform-sync-terraform-enterprise", + "vault/custom-secrets-engine-secrets": "/vault/tutorials/custom-secrets-engine/custom-secrets-engine-secrets", + "vault/custom-secrets-engine-role": "/vault/tutorials/custom-secrets-engine/custom-secrets-engine-role", + "vault/custom-secrets-engine-creds": "/vault/tutorials/custom-secrets-engine/custom-secrets-engine-creds", + "vault/custom-secrets-engine-config": "/vault/tutorials/custom-secrets-engine/custom-secrets-engine-config", + "vault/custom-secrets-engine-build": "/vault/tutorials/custom-secrets-engine/custom-secrets-engine-build", + "vault/custom-secrets-engine-backend": "/vault/tutorials/custom-secrets-engine/custom-secrets-engine-backend", + "onboarding/hcp-vault-w4-wrap-up": "/onboarding/hcp-vault-week-4/hcp-vault-w4-wrap-up", + "onboarding/hcp-vault-w4-welcome": "/onboarding/hcp-vault-week-4/hcp-vault-w4-welcome", + "onboarding/hcp-vault-w3-wrap-up": "/onboarding/hcp-vault-week-3/hcp-vault-w3-wrap-up", + "onboarding/hcp-vault-w3-welcome": "/onboarding/hcp-vault-week-3/hcp-vault-w3-welcome", + "onboarding/hcp-vault-w3-vault-basics": "/onboarding/hcp-vault-week-3/hcp-vault-w3-vault-basics", + "onboarding/hcp-vault-w3-encryption-as-service": "/onboarding/hcp-vault-week-3/hcp-vault-w3-encryption-as-service", + "onboarding/hcp-vault-w3-database-secrets": "/onboarding/hcp-vault-week-3/hcp-vault-w3-database-secrets", + "onboarding/hcp-vault-w2-wrap-up": "/onboarding/hcp-vault-week-2/hcp-vault-w2-wrap-up", + "onboarding/hcp-vault-w2-welcome": "/onboarding/hcp-vault-week-2/hcp-vault-w2-welcome", + "onboarding/hcp-vault-w1-wrap-up": "/onboarding/hcp-vault-week-1/hcp-vault-w1-wrap-up", + "onboarding/hcp-vault-w1-welcome": "/onboarding/hcp-vault-week-1/hcp-vault-w1-welcome", + "terraform/google-workspace": "/terraform/tutorials/it-saas/google-workspace", + "terraform/azure-ad": "/terraform/tutorials/it-saas/azure-ad", + "boundary/oidc-idp-groups": "/boundary/tutorials/identity-management/oidc-idp-groups", + "vault/write-a-policy-using-audit-logs": "/vault/tutorials/policies/write-a-policy-using-audit-logs", + "vault/write-a-policy-using-api-docs": "/vault/tutorials/policies/write-a-policy-using-api-docs", + "vault/versioned-kv": "/vault/tutorials/secrets-management/versioned-kv", + "vault/username-templating": "/vault/tutorials/secrets-management/username-templating", + "vault/usage-metrics": "/vault/tutorials/monitoring/usage-metrics", + "vault/troubleshooting-vault": "/vault/tutorials/monitoring/troubleshooting-vault", + "vault/troubleshoot-irrevocable-leases": "/vault/tutorials/monitoring/troubleshoot-irrevocable-leases", + "vault/transform": "/vault/tutorials/adp/transform", + "vault/tokens": "/vault/tutorials/tokens/tokens", + "vault/tokenization": "/vault/tutorials/adp/tokenization", + "vault/token-management": "/vault/tutorials/tokens/token-management", + "vault/terraform-secrets-engine": "/vault/tutorials/secrets-management/terraform-secrets-engine", + "vault/storage-migration-checklist": "/vault/tutorials/raft/storage-migration-checklist", + "vault/static-secrets": "/vault/tutorials/secrets-management/static-secrets", + "vault/ssh-otp": "/vault/tutorials/secrets-management/ssh-otp", + "vault/sop-upgrade": "/vault/tutorials/standard-procedures/sop-upgrade", + "vault/sop-restore": "/vault/tutorials/standard-procedures/sop-restore", + "vault/sop-backup": "/vault/tutorials/standard-procedures/sop-backup", + "vault/sentinel": "/vault/tutorials/policies/sentinel", + "vault/sentinel-policy-examples": "/vault/tutorials/policies/sentinel-policy-examples", + "vault/sentinel-http-import": "/vault/tutorials/policies/sentinel-http-import", + "vault/secure-introduction": "/vault/tutorials/app-integration/secure-introduction", + "vault/seal-wrap": "/vault/tutorials/auto-unseal/seal-wrap", + "vault/resource-quotas": "/vault/tutorials/operations/resource-quotas", + "vault/rekeying-and-rotating": "/vault/tutorials/operations/rekeying-and-rotating", + "vault/reference-architecture": "/vault/tutorials/day-one-consul/reference-architecture", + "vault/recovery-mode": "/vault/tutorials/monitoring/recovery-mode", + "vault/raft-storage": "/vault/tutorials/raft/raft-storage", + "vault/raft-storage-aws": "/vault/tutorials/raft/raft-storage-aws", + "vault/raft-reference-architecture": "/vault/tutorials/day-one-raft/raft-reference-architecture", + "vault/raft-migration": "/vault/tutorials/raft/raft-migration", + "vault/raft-lost-quorum": "/vault/tutorials/raft/raft-lost-quorum", + "vault/raft-ha-storage": "/vault/tutorials/raft/raft-ha-storage", + "vault/raft-deployment-guide": "/vault/tutorials/day-one-raft/raft-deployment-guide", + "vault/raft-autopilot": "/vault/tutorials/raft/raft-autopilot", + "vault/query-audit-device-logs": "/vault/tutorials/monitoring/query-audit-device-logs", + "vault/production-hardening": "/vault/tutorials/operations/production-hardening", + "vault/policy-templating": "/vault/tutorials/policies/policy-templating", + "vault/policies": "/vault/tutorials/policies/policies", + "vault/plugin-backends": "/vault/tutorials/app-integration/plugin-backends", + "vault/pki-engine": "/vault/tutorials/secrets-management/pki-engine", + "vault/performance-tuning": "/vault/tutorials/operations/performance-tuning", + "vault/performance-standbys": "/vault/tutorials/enterprise/performance-standbys", + "vault/performance-replication": "/vault/tutorials/enterprise/performance-replication", + "vault/pattern-unseal": "/vault/tutorials/recommended-patterns/pattern-unseal", + "vault/pattern-policy-templates": "/vault/tutorials/recommended-patterns/pattern-policy-templates", + "vault/pattern-centralized-secrets": "/vault/tutorials/recommended-patterns/pattern-centralized-secrets", + "vault/pattern-auto-unseal": "/vault/tutorials/recommended-patterns/pattern-auto-unseal", + "vault/pattern-approle": "/vault/tutorials/recommended-patterns/pattern-approle", + "vault/paths-filter": "/vault/tutorials/enterprise/paths-filter", + "vault/password-policies": "/vault/tutorials/policies/password-policies", + "vault/openldap": "/vault/tutorials/secrets-management/openldap", + "vault/oidc-auth": "/vault/tutorials/auth-methods/oidc-auth", + "vault/oidc-auth-azure": "/vault/tutorials/auth-methods/oidc-auth-azure", + "vault/namespaces": "/vault/tutorials/enterprise/namespaces", + "vault/multi-cluster-architecture": "/vault/tutorials/day-one-raft/multi-cluster-architecture", + "vault/monitor-telemetry-audit-splunk": "/vault/tutorials/monitoring/monitor-telemetry-audit-splunk", + "vault/monitor-replication": "/vault/tutorials/monitoring/monitor-replication", + "vault/kubernetes-troubleshooting": "/vault/tutorials/kubernetes/kubernetes-troubleshooting", + "vault/kubernetes-sidecar": "/vault/tutorials/kubernetes/kubernetes-sidecar", + "vault/kubernetes-security-concerns": "/vault/tutorials/kubernetes/kubernetes-security-concerns", + "vault/kubernetes-secret-store-driver": "/vault/tutorials/kubernetes/kubernetes-secret-store-driver", + "vault/kubernetes-raft-deployment-guide": "/vault/tutorials/kubernetes/kubernetes-raft-deployment-guide", + "vault/kubernetes-openshift": "/vault/tutorials/kubernetes/kubernetes-openshift", + "vault/kubernetes-minikube-consul": "/vault/tutorials/kubernetes/kubernetes-minikube-consul", + "vault/kubernetes-google-cloud-gke": "/vault/tutorials/kubernetes/kubernetes-google-cloud-gke", + "vault/kubernetes-external-vault": "/vault/tutorials/kubernetes/kubernetes-external-vault", + "vault/kubernetes-cert-manager": "/vault/tutorials/kubernetes/kubernetes-cert-manager", + "vault/kubernetes-azure-aks": "/vault/tutorials/kubernetes/kubernetes-azure-aks", + "vault/kubernetes-amazon-eks": "/vault/tutorials/kubernetes/kubernetes-amazon-eks", + "vault/kmip-engine": "/vault/tutorials/adp/kmip-engine", + "vault/key-management-secrets-engine-azure-key-vault": "/vault/tutorials/adp/key-management-secrets-engine-azure-key-vault", + "vault/inspect-data-integrated-storage": "/vault/tutorials/monitoring/inspect-data-integrated-storage", + "vault/inspecting-data-consul": "/vault/tutorials/monitoring/inspecting-data-consul", + "vault/inspect-data-boltdb": "/vault/tutorials/monitoring/inspect-data-boltdb", + "vault/identity": "/vault/tutorials/auth-methods/identity", + "vault/hsm-entropy": "/vault/tutorials/enterprise/hsm-entropy", + "vault/hcdiag-with-vault": "/vault/tutorials/monitoring/hcdiag-with-vault", + "vault/ha-with-consul": "/vault/tutorials/day-one-consul/ha-with-consul", + "vault/github-actions": "/vault/tutorials/app-integration/github-actions", + "vault/getting-started-ui": "/vault/tutorials/getting-started/getting-started-ui", + "vault/getting-started-secrets-ui": "/vault/tutorials/getting-started-ui/getting-started-secrets-ui", + "vault/getting-started-secrets-engines": "/vault/tutorials/getting-started/getting-started-secrets-engines", + "vault/getting-started-policies": "/vault/tutorials/getting-started/getting-started-policies", + "vault/getting-started-policies-ui": "/vault/tutorials/getting-started-ui/getting-started-policies-ui", + "vault/getting-started-next-steps": "/vault/tutorials/getting-started/getting-started-next-steps", + "vault/getting-started-intro": "/vault/tutorials/getting-started/getting-started-intro", + "vault/getting-started-intro-ui": "/vault/tutorials/getting-started-ui/getting-started-intro-ui", + "vault/getting-started-install": "/vault/tutorials/getting-started/getting-started-install", + "vault/getting-started-help": "/vault/tutorials/getting-started/getting-started-help", + "vault/getting-started-first-secret": "/vault/tutorials/getting-started/getting-started-first-secret", + "vault/getting-started-dynamic-secrets": "/vault/tutorials/getting-started/getting-started-dynamic-secrets", + "vault/getting-started-dev-server": "/vault/tutorials/getting-started/getting-started-dev-server", + "vault/getting-started-deploy": "/vault/tutorials/getting-started/getting-started-deploy", + "vault/getting-started-authentication": "/vault/tutorials/getting-started/getting-started-authentication", + "vault/getting-started-auth-ui": "/vault/tutorials/getting-started-ui/getting-started-auth-ui", + "vault/getting-started-apis": "/vault/tutorials/getting-started/getting-started-apis", + "vault/getting-started-api-ui": "/vault/tutorials/getting-started-ui/getting-started-api-ui", + "vault/generate-root": "/vault/tutorials/operations/generate-root", + "vault/eaas-transit": "/vault/tutorials/encryption-as-a-service/eaas-transit", + "vault/eaas-transit-rewrap": "/vault/tutorials/encryption-as-a-service/eaas-transit-rewrap", + "vault/eaas-spring-demo": "/vault/tutorials/encryption-as-a-service/eaas-spring-demo", + "vault/dotnet-vault-agent": "/vault/tutorials/app-integration/dotnet-vault-agent", + "vault/dotnet-httpclient": "/vault/tutorials/app-integration/dotnet-httpclient", + "vault/disaster-recovery": "/vault/tutorials/enterprise/disaster-recovery", + "vault/diagnose-startup-issues": "/vault/tutorials/monitoring/diagnose-startup-issues", + "vault/deployment-guide": "/vault/tutorials/day-one-consul/deployment-guide", + "vault/database-secrets": "/vault/tutorials/db-credentials/database-secrets", + "vault/database-secrets-couchbase": "/vault/tutorials/db-credentials/database-secrets-couchbase", + "vault/database-root-rotation": "/vault/tutorials/db-credentials/database-root-rotation", + "vault/database-mongodb": "/vault/tutorials/db-credentials/database-mongodb", + "vault/database-creds-rotation": "/vault/tutorials/db-credentials/database-creds-rotation", + "vault/cubbyhole-response-wrapping": "/vault/tutorials/secrets-management/cubbyhole-response-wrapping", + "vault/control-groups": "/vault/tutorials/enterprise/control-groups", + "vault/configure-vault": "/vault/tutorials/operations/configure-vault", + "vault/codify-mgmt-vault-terraform": "/vault/tutorials/operations/codify-mgmt-vault-terraform", + "vault/codify-mgmt-enterprise": "/vault/tutorials/operations/codify-mgmt-enterprise", + "vault/browser-plugin": "/vault/tutorials/secrets-management/browser-plugin", + "vault/blocked-audit-devices": "/vault/tutorials/monitoring/blocked-audit-devices", + "vault/batch-tokens": "/vault/tutorials/tokens/batch-tokens", + "vault/azure-secrets": "/vault/tutorials/secrets-management/azure-secrets", + "vault/aws-lambda": "/vault/tutorials/app-integration/aws-lambda", + "vault/autounseal-transit": "/vault/tutorials/auto-unseal/autounseal-transit", + "vault/autounseal-gcp-kms": "/vault/tutorials/auto-unseal/autounseal-gcp-kms", + "vault/autounseal-azure-keyvault": "/vault/tutorials/auto-unseal/autounseal-azure-keyvault", + "vault/autounseal-aws-kms": "/vault/tutorials/auto-unseal/autounseal-aws-kms", + "vault/associate-study": "/vault/tutorials/associate-cert/associate-study", + "vault/associate-review": "/vault/tutorials/associate-cert/associate-review", + "vault/associate-questions": "/vault/tutorials/associate-cert/associate-questions", + "vault/approle": "/vault/tutorials/auth-methods/approle", + "vault/approle-trusted-entities": "/vault/tutorials/auth-methods/approle-trusted-entities", + "vault/approle-best-practices": "/vault/tutorials/auth-methods/approle-best-practices", + "vault/application-integration": "/vault/tutorials/app-integration/application-integration", + "vault/agent-windows-service": "/vault/tutorials/vault-agent/agent-windows-service", + "vault/agent-templates": "/vault/tutorials/vault-agent/agent-templates", + "vault/agent-kubernetes": "/vault/tutorials/kubernetes/agent-kubernetes", + "vault/agent-caching": "/vault/tutorials/vault-agent/agent-caching", + "vault/agent-aws": "/vault/tutorials/vault-agent/agent-aws", + "vault/active-directory": "/vault/tutorials/secrets-management/active-directory", + "vagrant/getting-started-up": "/vagrant/tutorials/getting-started/getting-started-up", + "vagrant/getting-started-teardown": "/vagrant/tutorials/getting-started/getting-started-teardown", + "vagrant/getting-started-synced-folders": "/vagrant/tutorials/getting-started/getting-started-synced-folders", + "vagrant/getting-started-share": "/vagrant/tutorials/getting-started/getting-started-share", + "vagrant/getting-started-rebuild": "/vagrant/tutorials/getting-started/getting-started-rebuild", + "vagrant/getting-started-provisioning": "/vagrant/tutorials/networking-provisioning-operations/getting-started-provisioning", + "vagrant/getting-started-providers": "/vagrant/tutorials/networking-provisioning-operations/getting-started-providers", + "vagrant/getting-started-project-setup": "/vagrant/tutorials/getting-started/getting-started-project-setup", + "vagrant/getting-started-networking": "/vagrant/tutorials/networking-provisioning-operations/getting-started-networking", + "vagrant/getting-started-install": "/vagrant/tutorials/getting-started/getting-started-install", + "vagrant/getting-started-index": "/vagrant/tutorials/getting-started/getting-started-index", + "vagrant/getting-started-boxes": "/vagrant/tutorials/getting-started/getting-started-boxes", + "terraform/vsphere-provider": "/terraform/tutorials/virtual-machine/vsphere-provider", + "terraform/versions": "/terraform/tutorials/configuration-language/versions", + "terraform/verify-archive": "/terraform/tutorials/cli/verify-archive", + "terraform/variables": "/terraform/tutorials/configuration-language/variables", + "terraform/troubleshooting-workflow": "/terraform/tutorials/configuration-language/troubleshooting-workflow", + "terraform/tfe-provider-run-triggers": "/terraform/tutorials/automation/tfe-provider-run-triggers", + "terraform/state-import": "/terraform/tutorials/state/state-import", + "terraform/state-cli": "/terraform/tutorials/state/state-cli", + "terraform/spotify-playlist": "/terraform/tutorials/community-providers/spotify-playlist", + "terraform/sentinel-testing": "/terraform/tutorials/policy/sentinel-testing", + "terraform/sentinel-policy": "/terraform/tutorials/policy/sentinel-policy", + "terraform/sentinel-install": "/terraform/tutorials/policy/sentinel-install", + "terraform/sentinel-import": "/terraform/tutorials/policy/sentinel-import", + "terraform/sentinel-cloud-integration": "/terraform/tutorials/policy/sentinel-cloud-integration", + "terraform/sensitive-variables": "/terraform/tutorials/configuration-language/sensitive-variables", + "terraform/secrets-vault": "/terraform/tutorials/secrets/secrets-vault", + "terraform/resource": "/terraform/tutorials/configuration-language/resource", + "terraform/resource-targeting": "/terraform/tutorials/state/resource-targeting", + "terraform/resource-lifecycle": "/terraform/tutorials/state/resource-lifecycle", + "terraform/resource-drift": "/terraform/tutorials/state/resource-drift", + "terraform/refresh": "/terraform/tutorials/state/refresh", + "terraform/provider-versioning": "/terraform/tutorials/configuration-language/provider-versioning", + "terraform/policy-quickstart": "/terraform/tutorials/cloud-get-started/policy-quickstart", + "terraform/providers-plugin-framework-resource-create": "/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-resource-create", + "terraform/pattern-module-creation": "/terraform/tutorials/modules/pattern-module-creation", + "terraform/pattern-backups": "/terraform/tutorials/recommended-patterns/pattern-backups", + "terraform/packer": "/terraform/tutorials/provision/packer", + "terraform/outputs": "/terraform/tutorials/configuration-language/outputs", + "terraform/organize-configuration": "/terraform/tutorials/modules/organize-configuration", + "terraform/oci-variables": "/terraform/tutorials/oci-get-started/oci-variables", + "terraform/oci-outputs": "/terraform/tutorials/oci-get-started/oci-outputs", + "terraform/oci-destroy": "/terraform/tutorials/oci-get-started/oci-destroy", + "terraform/oci-change": "/terraform/tutorials/oci-get-started/oci-change", + "terraform/oci-build": "/terraform/tutorials/oci-get-started/oci-build", + "terraform/multicloud-kubernetes": "/terraform/tutorials/networking/multicloud-kubernetes", + "terraform/module": "/terraform/tutorials/modules/module", + "terraform/module-use": "/terraform/tutorials/modules/module-use", + "terraform/module-private-registry-share": "/terraform/tutorials/modules/module-private-registry-share", + "terraform/private-registry-add": "/terraform/tutorials/modules/private-registry-add", + "terraform/module-create": "/terraform/tutorials/modules/module-create", + "terraform/locals": "/terraform/tutorials/configuration-language/locals", + "terraform/lambda-api-gateway": "/terraform/tutorials/aws/lambda-api-gateway", + "terraform/kubernetes-provider": "/terraform/tutorials/kubernetes/kubernetes-provider", + "terraform/kubernetes-operator": "/terraform/tutorials/kubernetes/kubernetes-operator", + "terraform/kubernetes-crd-faas": "/terraform/tutorials/kubernetes/kubernetes-crd-faas", + "terraform/kubernetes-consul-vault-pipeline": "/terraform/tutorials/kubernetes/kubernetes-consul-vault-pipeline", + "terraform/install-cli": "/terraform/tutorials/aws-get-started/install-cli", + "terraform/infrastructure-as-code": "/terraform/tutorials/aws-get-started/infrastructure-as-code", + "terraform/heroku-provider": "/terraform/tutorials/applications/heroku-provider", + "terraform/helm-provider": "/terraform/tutorials/kubernetes/helm-provider", + "terraform/google-cloud-platform-variables": "/terraform/tutorials/gcp-get-started/google-cloud-platform-variables", + "terraform/google-cloud-platform-outputs": "/terraform/tutorials/gcp-get-started/google-cloud-platform-outputs", + "terraform/google-cloud-platform-destroy": "/terraform/tutorials/gcp-get-started/google-cloud-platform-destroy", + "terraform/google-cloud-platform-change": "/terraform/tutorials/gcp-get-started/google-cloud-platform-change", + "terraform/google-cloud-platform-build": "/terraform/tutorials/gcp-get-started/google-cloud-platform-build", + "terraform/gke": "/terraform/tutorials/kubernetes/gke", + "terraform/github-oauth": "/terraform/tutorials/cloud/github-oauth", + "terraform/github-actions": "/terraform/tutorials/automation/github-actions", + "terraform/functions": "/terraform/tutorials/configuration-language/functions", + "terraform/for-each": "/terraform/tutorials/configuration-language/for-each", + "terraform/expressions": "/terraform/tutorials/configuration-language/expressions", + "terraform/eks": "/terraform/tutorials/kubernetes/eks", + "terraform/docker-variables": "/terraform/tutorials/docker-get-started/docker-variables", + "terraform/docker-outputs": "/terraform/tutorials/docker-get-started/docker-outputs", + "terraform/docker-destroy": "/terraform/tutorials/docker-get-started/docker-destroy", + "terraform/docker-change": "/terraform/tutorials/docker-get-started/docker-change", + "terraform/docker-build": "/terraform/tutorials/docker-get-started/docker-build", + "terraform/digitalocean-provider": "/terraform/tutorials/applications/digitalocean-provider", + "terraform/dependencies": "/terraform/tutorials/configuration-language/dependencies", + "terraform/datadog-provider": "/terraform/tutorials/applications/datadog-provider", + "terraform/data-sources": "/terraform/tutorials/configuration-language/data-sources", + "terraform/count": "/terraform/tutorials/configuration-language/count", + "terraform/cost-estimation": "/terraform/tutorials/cloud-get-started/cost-estimation", + "terraform/cloudflare-static-website": "/terraform/tutorials/applications/cloudflare-static-website", + "terraform/cloud-workspace-create": "/terraform/tutorials/cloud-get-started/cloud-workspace-create", + "terraform/cloud-workspace-configure": "/terraform/tutorials/cloud-get-started/cloud-workspace-configure", + "terraform/cloud-versions": "/terraform/tutorials/cloud/cloud-versions", + "terraform/cloud-state-api": "/terraform/tutorials/cloud/cloud-state-api", + "terraform/cloud-sign-up": "/terraform/tutorials/cloud-get-started/cloud-sign-up", + "terraform/cloud-run-triggers": "/terraform/tutorials/cloud/cloud-run-triggers", + "terraform/cloud-permissions": "/terraform/tutorials/cloud/cloud-permissions", + "terraform/cloud-migrate": "/terraform/tutorials/cloud/cloud-migrate", + "terraform/cloud-login": "/terraform/tutorials/cloud-get-started/cloud-login", + "terraform/cloud-init": "/terraform/tutorials/provision/cloud-init", + "terraform/cloud-destroy": "/terraform/tutorials/cloud-get-started/cloud-destroy", + "terraform/cloud-change": "/terraform/tutorials/cloud-get-started/cloud-change", + "terraform/cloud-agents": "/terraform/tutorials/cloud/cloud-agents", + "terraform/circle-ci": "/terraform/tutorials/automation/circle-ci", + "terraform/cdktf-install": "/terraform/tutorials/cdktf/cdktf-install", + "terraform/cdktf-build": "/terraform/tutorials/cdktf/cdktf-build", + "terraform/cdktf-assets-stacks-lambda": "/terraform/tutorials/cdktf/cdktf-assets-stacks-lambda", + "terraform/blue-green-canary-tests-deployments": "/terraform/tutorials/aws/blue-green-canary-tests-deployments", + "terraform/azure-variables": "/terraform/tutorials/azure-get-started/azure-variables", + "terraform/azure-remote": "/terraform/tutorials/azure-get-started/azure-remote", + "terraform/azure-outputs": "/terraform/tutorials/azure-get-started/azure-outputs", + "terraform/azure-destroy": "/terraform/tutorials/azure-get-started/azure-destroy", + "terraform/azure-change": "/terraform/tutorials/azure-get-started/azure-change", + "terraform/azure-build": "/terraform/tutorials/azure-get-started/azure-build", + "terraform/aws-variables": "/terraform/tutorials/aws-get-started/aws-variables", + "terraform/aws-remote": "/terraform/tutorials/aws-get-started/aws-remote", + "terraform/aws-rds": "/terraform/tutorials/aws/aws-rds", + "terraform/aws-outputs": "/terraform/tutorials/aws-get-started/aws-outputs", + "terraform/aws-iam-policy": "/terraform/tutorials/aws/aws-iam-policy", + "terraform/aws-destroy": "/terraform/tutorials/aws-get-started/aws-destroy", + "terraform/aws-default-tags": "/terraform/tutorials/aws/aws-default-tags", + "terraform/aws-change": "/terraform/tutorials/aws-get-started/aws-change", + "terraform/aws-build": "/terraform/tutorials/aws-get-started/aws-build", + "terraform/aws-assumerole": "/terraform/tutorials/aws/aws-assumerole", + "terraform/automate-terraform": "/terraform/tutorials/automation/automate-terraform", + "terraform/associate-questions": "/terraform/tutorials/certification-003/associate-questions", + "terraform/aks": "/terraform/tutorials/kubernetes/aks", + "packer/hcl2-upgrade": "/packer/tutorials/configuration-language/hcl2-upgrade", + "packer/get-started-install-cli": "/packer/tutorials/docker-get-started/get-started-install-cli", + "packer/docker-get-started-variables": "/packer/tutorials/docker-get-started/docker-get-started-variables", + "packer/docker-get-started-provision": "/packer/tutorials/docker-get-started/docker-get-started-provision", + "packer/docker-get-started-post-processors": "/packer/tutorials/docker-get-started/docker-get-started-post-processors", + "packer/docker-get-started-parallel-builds": "/packer/tutorials/docker-get-started/docker-get-started-parallel-builds", + "packer/docker-get-started-build-image": "/packer/tutorials/docker-get-started/docker-get-started-build-image", + "packer/aws-windows-image": "/packer/tutorials/cloud-production/aws-windows-image", + "packer/aws-get-started-variables": "/packer/tutorials/aws-get-started/aws-get-started-variables", + "packer/aws-get-started-provision": "/packer/tutorials/aws-get-started/aws-get-started-provision", + "packer/aws-get-started-post-processors-vagrant": "/packer/tutorials/aws-get-started/aws-get-started-post-processors-vagrant", + "packer/aws-get-started-parallel-builds": "/packer/tutorials/aws-get-started/aws-get-started-parallel-builds", + "packer/aws-get-started-build-image": "/packer/tutorials/aws-get-started/aws-get-started-build-image", + "nomad/windows-agent": "/nomad/tutorials/windows/windows-agent", + "nomad/web-ui-workload-info": "/nomad/tutorials/web-ui/web-ui-workload-info", + "nomad/web-ui-submit-job": "/nomad/tutorials/web-ui/web-ui-submit-job", + "nomad/web-ui-considerations": "/nomad/tutorials/web-ui/web-ui-considerations", + "nomad/web-ui-cluster-info": "/nomad/tutorials/web-ui/web-ui-cluster-info", + "nomad/web-ui-access": "/nomad/tutorials/web-ui/web-ui-access", + "nomad/vault-postgres": "/nomad/tutorials/integrate-vault/vault-postgres", + "nomad/vault-pki-nomad": "/nomad/tutorials/integrate-vault/vault-pki-nomad", + "nomad/vault-nomad-secrets": "/nomad/tutorials/integrate-vault/vault-nomad-secrets", + "nomad/topology-visualization": "/nomad/tutorials/web-ui/topology-visualization", + "nomad/task-dependencies-interjob": "/nomad/tutorials/task-deps/task-dependencies-interjob", + "nomad/stateful-workloads": "/nomad/tutorials/stateful-workloads/stateful-workloads", + "nomad/stateful-workloads-portworx": "/nomad/tutorials/stateful-workloads/stateful-workloads-portworx", + "nomad/stateful-workloads-host-volumes": "/nomad/tutorials/stateful-workloads/stateful-workloads-host-volumes", + "nomad/stateful-workloads-csi-volumes": "/nomad/tutorials/stateful-workloads/stateful-workloads-csi-volumes", + "nomad/spread": "/nomad/tutorials/advanced-scheduling/spread", + "nomad/sentinel": "/nomad/tutorials/governance-and-policy/sentinel", + "nomad/security-gossip-encryption": "/nomad/tutorials/transport-security/security-gossip-encryption", + "nomad/security-enable-tls": "/nomad/tutorials/transport-security/security-enable-tls", + "nomad/security-concepts": "/nomad/tutorials/transport-security/security-concepts", + "nomad/reverse-proxy-ui": "/nomad/tutorials/manage-clusters/reverse-proxy-ui", + "nomad/quotas": "/nomad/tutorials/governance-and-policy/quotas", + "nomad/prometheus-metrics": "/nomad/tutorials/manage-clusters/prometheus-metrics", + "nomad/production-reference-architecture-vm-with-consul": "/nomad/tutorials/enterprise/production-reference-architecture-vm-with-consul", + "nomad/production-deployment-guide-vm-with-consul": "/nomad/tutorials/enterprise/production-deployment-guide-vm-with-consul", + "nomad/preemption": "/nomad/tutorials/advanced-scheduling/preemption", + "nomad/plugin-lxc": "/nomad/tutorials/plugins/plugin-lxc", + "nomad/outage-recovery": "/nomad/tutorials/manage-clusters/outage-recovery", + "nomad/node-drain": "/nomad/tutorials/manage-clusters/node-drain", + "nomad/namespaces": "/nomad/tutorials/manage-clusters/namespaces", + "nomad/multiregion-deployments": "/nomad/tutorials/manage-clusters/multiregion-deployments", + "nomad/memory-oversubscription": "/nomad/tutorials/advanced-scheduling/memory-oversubscription", + "nomad/load-balancing": "/nomad/tutorials/load-balancing/load-balancing", + "nomad/load-balancing-traefik": "/nomad/tutorials/load-balancing/load-balancing-traefik", + "nomad/load-balancing-nginx": "/nomad/tutorials/load-balancing/load-balancing-nginx", + "nomad/load-balancing-haproxy": "/nomad/tutorials/load-balancing/load-balancing-haproxy", + "nomad/load-balancing-fabio": "/nomad/tutorials/load-balancing/load-balancing-fabio", + "nomad/levant-abstract-jobs": "/nomad/tutorials/templates/levant-abstract-jobs", + "nomad/jobs": "/nomad/tutorials/manage-jobs/jobs", + "nomad/jobs-utilization": "/nomad/tutorials/manage-jobs/jobs-utilization", + "nomad/jobs-submit": "/nomad/tutorials/manage-jobs/jobs-submit", + "nomad/jobs-inspect": "/nomad/tutorials/manage-jobs/jobs-inspect", + "nomad/jobs-configuring": "/nomad/tutorials/manage-jobs/jobs-configuring", + "nomad/jobs-accessing-logs": "/nomad/tutorials/manage-jobs/jobs-accessing-logs", + "nomad/job-update-strategies": "/nomad/tutorials/job-updates/job-update-strategies", + "nomad/job-update-handle-signals": "/nomad/tutorials/job-updates/job-update-handle-signals", + "nomad/job-spec-parameterized": "/nomad/tutorials/job-specifications/job-spec-parameterized", + "nomad/job-spec-java-windows": "/nomad/tutorials/job-specifications/job-spec-java-windows", + "nomad/job-spec-java-linux": "/nomad/tutorials/job-specifications/job-spec-java-linux", + "nomad/job-rolling-update": "/nomad/tutorials/job-updates/job-rolling-update", + "nomad/job-blue-green-and-canary-deployments": "/nomad/tutorials/job-updates/job-blue-green-and-canary-deployments", + "nomad/horizontal-cluster-scaling": "/nomad/tutorials/autoscaler/horizontal-cluster-scaling", + "nomad/horizontal-cluster-scaling-on-demand-batch": "/nomad/tutorials/autoscaler/horizontal-cluster-scaling-on-demand-batch", + "nomad/hashicorp-enterprise-license": "/nomad/tutorials/enterprise/hashicorp-enterprise-license", + "nomad/governance-and-policy": "/nomad/tutorials/governance-and-policy/governance-and-policy", + "nomad/go-template-syntax": "/nomad/tutorials/templates/go-template-syntax", + "nomad/format-output-with-templates": "/nomad/tutorials/templates/format-output-with-templates", + "nomad/federation": "/nomad/tutorials/manage-clusters/federation", + "nomad/failures": "/nomad/tutorials/job-failure-handling/failures", + "nomad/failures-restart": "/nomad/tutorials/job-failure-handling/failures-restart", + "nomad/failures-reschedule": "/nomad/tutorials/job-failure-handling/failures-reschedule", + "nomad/failures-check-restart": "/nomad/tutorials/job-failure-handling/failures-check-restart", + "nomad/exec-users-host-volumes": "/nomad/tutorials/stateful-workloads/exec-users-host-volumes", + "nomad/event-stream": "/nomad/tutorials/integrate-nomad/event-stream", + "nomad/dynamic-application-sizing": "/nomad/tutorials/autoscaler/dynamic-application-sizing", + "nomad/dynamic-application-sizing-concepts": "/nomad/tutorials/autoscaler/dynamic-application-sizing-concepts", + "nomad/dry-jobs-levant": "/nomad/tutorials/templates/dry-jobs-levant", + "nomad/consul-service-mesh": "/nomad/tutorials/integrate-consul/consul-service-mesh", + "nomad/clustering": "/nomad/tutorials/manage-clusters/clustering", + "nomad/autoscaler-vagrant-demo": "/nomad/tutorials/autoscaler/autoscaler-vagrant-demo", + "nomad/autopilot": "/nomad/tutorials/manage-clusters/autopilot", + "nomad/affinity": "/nomad/tutorials/advanced-scheduling/affinity", + "nomad/advanced-scheduling": "/nomad/tutorials/advanced-scheduling/advanced-scheduling", + "nomad/access-control": "/nomad/tutorials/access-control/access-control", + "nomad/access-control-tokens": "/nomad/tutorials/access-control/access-control-tokens", + "nomad/access-control-policies": "/nomad/tutorials/access-control/access-control-policies", + "nomad/access-control-create-policy": "/nomad/tutorials/access-control/access-control-create-policy", + "nomad/access-control-bootstrap": "/nomad/tutorials/access-control/access-control-bootstrap", + "consul/vault-pki-consul-secure-tls": "/consul/tutorials/vault-secure/vault-pki-consul-secure-tls", + "consul/vault-kv-consul-secure-gossip": "/consul/tutorials/vault-secure/vault-kv-consul-secure-gossip", + "consul/vault-consul-secrets": "/consul/tutorials/vault-secure/vault-consul-secrets", + "consul/upgrade-federated-environment": "/consul/tutorials/datacenter-operations/upgrade-federated-environment", + "consul/upgrade-automation": "/consul/tutorials/datacenter-operations/upgrade-automation", + "consul/troubleshooting": "/consul/tutorials/datacenter-operations/troubleshooting", + "consul/tls-encryption-secure": "/consul/tutorials/security/tls-encryption-secure", + "consul/tls-encryption-secure-existing-datacenter": "/consul/tutorials/security-operations/tls-encryption-secure-existing-datacenter", + "consul/tls-encryption-openssl-secure": "/consul/tutorials/security-operations/tls-encryption-openssl-secure", + "consul/terraform-consul-provider": "/consul/tutorials/developer-discovery/terraform-consul-provider", + "consul/terminating-gateways-connect-external-services": "/consul/tutorials/developer-mesh/terminating-gateways-connect-external-services", + "consul/sync-pivotal-cloud-services": "/consul/tutorials/cloud-integrations/sync-pivotal-cloud-services", + "consul/sync-aws-services": "/consul/tutorials/cloud-integrations/sync-aws-services", + "consul/single-sign-on-auth0": "/consul/tutorials/datacenter-operations/single-sign-on-auth0", + "consul/service-registration-health-checks": "/consul/tutorials/developer-discovery/service-registration-health-checks", + "consul/service-registration-external-services": "/consul/tutorials/developer-discovery/service-registration-external-services", + "consul/service-mesh": "/consul/tutorials/kubernetes-deploy/service-mesh", + "consul/service-mesh-zero-trust-network": "/consul/tutorials/kubernetes-features/service-mesh-zero-trust-network", + "consul/service-mesh-with-envoy-proxy": "/consul/tutorials/developer-mesh/service-mesh-with-envoy-proxy", + "consul/service-mesh-visualization": "/consul/tutorials/developer-mesh/service-mesh-visualization", + "consul/service-mesh-traffic-management": "/consul/tutorials/kubernetes-features/service-mesh-traffic-management", + "consul/service-mesh-terminating-gateways": "/consul/tutorials/developer-mesh/service-mesh-terminating-gateways", + "consul/service-mesh-production-checklist": "/consul/tutorials/developer-mesh/service-mesh-production-checklist", + "consul/service-mesh-observability": "/consul/tutorials/kubernetes-features/service-mesh-observability", + "consul/service-mesh-ingress-gateways": "/consul/tutorials/developer-mesh/service-mesh-ingress-gateways", + "consul/service-mesh-gateways": "/consul/tutorials/developer-mesh/service-mesh-gateways", + "consul/service-mesh-circuit-breaking": "/consul/tutorials/developer-mesh/service-mesh-circuit-breaking", + "consul/service-mesh-application-secure-networking": "/consul/tutorials/kubernetes/service-mesh-application-secure-networking", + "consul/reference-architecture": "/consul/tutorials/production-deploy/reference-architecture", + "consul/redundancy-zones": "/consul/tutorials/datacenter-operations/redundancy-zones", + "consul/recovery-outage": "/consul/tutorials/datacenter-operations/recovery-outage", + "consul/recovery-outage-primary": "/consul/tutorials/datacenter-operations/recovery-outage-primary", + "consul/production-checklist": "/consul/tutorials/production-deploy/production-checklist", + "consul/namespaces-share-datacenter-access": "/consul/tutorials/namespaces/namespaces-share-datacenter-access", + "consul/namespaces-secure-shared-access": "/consul/tutorials/namespaces/namespaces-secure-shared-access", + "consul/monitor-with-appdynamics": "/consul/tutorials/cloud-integrations/monitor-with-appdynamics", + "consul/monitor-health-telegraf": "/consul/tutorials/day-2-operations/monitor-health-telegraf", + "consul/monitor-datacenter-health": "/consul/tutorials/day-2-operations/monitor-datacenter-health", + "consul/load-balancing-nginx": "/consul/tutorials/load-balancing/load-balancing-nginx", + "consul/load-balancing-nginx-plus": "/consul/tutorials/load-balancing/load-balancing-nginx-plus", + "consul/load-balancing-haproxy": "/consul/tutorials/load-balancing/load-balancing-haproxy", + "consul/load-balancing-f5": "/consul/tutorials/load-balancing/load-balancing-f5", + "consul/load-balancing-envoy": "/consul/tutorials/developer-mesh/load-balancing-envoy", + "consul/kubernetes-secure-agents": "/consul/tutorials/kubernetes/kubernetes-secure-agents", + "consul/kubernetes-scope-microservice": "/consul/tutorials/microservices/kubernetes-scope-microservice", + "consul/kubernetes-reference-architecture": "/consul/tutorials/kubernetes/kubernetes-reference-architecture", + "consul/kubernetes-openshift-red-hat": "/consul/tutorials/kubernetes/kubernetes-openshift-red-hat", + "consul/kubernetes-model-the-monolith": "/consul/tutorials/microservices/kubernetes-model-the-monolith", + "consul/kubernetes-minikube": "/consul/tutorials/kubernetes/kubernetes-minikube", + "consul/kubernetes-migrate-to-microservices": "/consul/tutorials/microservices/kubernetes-migrate-to-microservices", + "consul/kubernetes-mesh-gateways": "/consul/tutorials/kubernetes/kubernetes-mesh-gateways", + "consul/kubernetes-layer7-observability": "/consul/tutorials/kubernetes/kubernetes-layer7-observability", + "consul/kubernetes-kind": "/consul/tutorials/kubernetes/kubernetes-kind", + "consul/kubernetes-gke-google": "/consul/tutorials/kubernetes/kubernetes-gke-google", + "consul/kubernetes-extract-microservice": "/consul/tutorials/microservices/kubernetes-extract-microservice", + "consul/kubernetes-eks-aws": "/consul/tutorials/kubernetes/kubernetes-eks-aws", + "consul/kubernetes-disaster-recovery": "/consul/tutorials/kubernetes-production/kubernetes-disaster-recovery", + "consul/kubernetes-deployment-guide": "/consul/tutorials/kubernetes/kubernetes-deployment-guide", + "consul/kubernetes-custom-resource-definitions": "/consul/tutorials/kubernetes/kubernetes-custom-resource-definitions", + "consul/kubernetes-consul-design-patterns": "/consul/tutorials/microservices/kubernetes-consul-design-patterns", + "consul/kubernetes-aks-azure": "/consul/tutorials/kubernetes/kubernetes-aks-azure", + "consul/gossip-encryption-secure": "/consul/tutorials/security/gossip-encryption-secure", + "consul/gossip-encryption-rotate": "/consul/tutorials/datacenter-operations/gossip-encryption-rotate", + "consul/virtual-machine-gs-service-discovery": "/consul/tutorials/get-started-vms/virtual-machine-gs-service-discovery", + "consul/get-started-key-value-store": "/consul/tutorials/interactive/get-started-key-value-store", + "consul/get-started-explore-the-ui": "/consul/tutorials/certification-associate-tutorials/get-started-explore-the-ui", + "consul/get-started-create-datacenter": "/consul/tutorials/certification-associate-tutorials/get-started-create-datacenter", + "consul/get-started-agent": "/consul/tutorials/certification-associate-tutorials/get-started-agent", + "consul/federation-network-areas": "/consul/tutorials/datacenter-operations/federation-network-areas", + "consul/federation-gossip-wan": "/consul/tutorials/networking/federation-gossip-wan", + "consul/docker-container-agents": "/consul/tutorials/day-0/docker-container-agents", + "consul/docker-compose-observability": "/consul/tutorials/docker/docker-compose-observability", + "consul/docker-compose-datacenter": "/consul/tutorials/docker/docker-compose-datacenter", + "consul/dns-forwarding": "/consul/tutorials/networking/dns-forwarding", + "consul/dns-caching": "/consul/tutorials/networking/dns-caching", + "consul/distributed-semaphore": "/consul/tutorials/developer-configuration/distributed-semaphore", + "consul/deployment-overview": "/consul/tutorials/production-deploy/deployment-overview", + "consul/deployment-next-steps": "/consul/tutorials/production-deploy/deployment-next-steps", + "consul/deployment-guide": "/consul/tutorials/production-deploy/deployment-guide", + "consul/consul-terraform-sync-run-and-inspect": "/consul/tutorials/network-infrastructure-automation/consul-terraform-sync-run-and-inspect", + "consul/consul-terraform-sync-module": "/consul/tutorials/network-infrastructure-automation/consul-terraform-sync-module", + "consul/consul-terraform-sync": "/consul/tutorials/network-infrastructure-automation/consul-terraform-sync", + "consul/consul-terraform-sync-a10-adc": "/consul/tutorials/network-infrastructure-automation/consul-terraform-sync-a10-adc", + "consul/consul-template": "/consul/tutorials/developer-configuration/consul-template", + "consul/backup-and-restore": "/consul/tutorials/production-deploy/backup-and-restore", + "consul/autopilot-datacenter-operations": "/consul/tutorials/datacenter-operations/autopilot-datacenter-operations", + "consul/automate-geo-failover": "/consul/tutorials/developer-discovery/automate-geo-failover", + "consul/audit-logging": "/consul/tutorials/datacenter-operations/audit-logging", + "consul/associate-study": "/consul/tutorials/certification/associate-study", + "consul/associate-review": "/consul/tutorials/certification/associate-review", + "consul/associate-questions": "/consul/tutorials/certification/associate-questions", + "consul/application-leader-elections": "/consul/tutorials/developer-configuration/application-leader-elections", + "consul/add-remove-servers": "/consul/tutorials/datacenter-operations/add-remove-servers", + "consul/access-control-troubleshoot": "/consul/tutorials/security/access-control-troubleshoot", + "consul/access-control-token-migration": "/consul/tutorials/security-operations/access-control-token-migration", + "consul/access-control-setup": "/consul/tutorials/day-0/access-control-setup", + "consul/access-control-setup-production": "/consul/tutorials/security/access-control-setup-production", + "consul/access-control-replication-multiple-datacenters": "/consul/tutorials/security-operations/access-control-replication-multiple-datacenters", + "consul/access-control-manage-policies": "/consul/tutorials/security/access-control-manage-policies", + "cloud/vault-policies": "/vault/tutorials/cloud/vault-policies", + "cloud/vault-ops": "/vault/tutorials/cloud/vault-ops", + "cloud/vault-namespaces": "/vault/tutorials/cloud/vault-namespaces", + "cloud/vault-introduction": "/vault/tutorials/cloud/vault-introduction", + "cloud/vault-first-secrets": "/vault/tutorials/cloud/vault-first-secrets", + "cloud/vault-eks": "/vault/tutorials/cloud-ops/vault-eks", + "cloud/vault-auth-method": "/vault/tutorials/cloud/vault-auth-method", + "cloud/terraform-hcp-provider-vault": "/vault/tutorials/cloud-ops/terraform-hcp-provider-vault", + "cloud/terraform-hcp-consul-provider": "/consul/tutorials/cloud-production/terraform-hcp-consul-provider", + "cloud/get-started-vault": "/vault/tutorials/cloud/get-started-vault", + "cloud/consul-hcp-migration": "/consul/tutorials/cloud-production/consul-hcp-migration", + "cloud/consul-hcp-federation": "/consul/tutorials/cloud-production/consul-hcp-federation", + "cloud/consul-deploy": "/hcp/tutorials/consul-cloud/consul-deploy", + "cloud/consul-client-eks": "/consul/tutorials/cloud-production/consul-client-eks", + "cloud/amazon-transit-gateway": "/hcp/tutorials/networking/amazon-transit-gateway", + "cloud/amazon-peering-hcp": "/hcp/tutorials/networking/amazon-peering-hcp", + "boundary/oss-vault-cred-brokering-quickstart": "/boundary/tutorials/credential-management/oss-vault-cred-brokering-quickstart", + "boundary/upgrade-version": "/boundary/tutorials/self-managed-deployment/upgrade-version", + "boundary/target-aware-workers": "/boundary/tutorials/worker-management/target-aware-workers", + "boundary/oss-manage-users-groups": "/boundary/tutorials/oss-administration/oss-manage-users-groups", + "boundary/oss-manage-targets": "/boundary/tutorials/oss-administration/oss-manage-targets", + "boundary/oss-manage-sessions": "/boundary/tutorials/oss-administration/oss-manage-sessions", + "boundary/oss-manage-scopes": "/boundary/tutorials/oss-administration/oss-manage-scopes", + "boundary/oss-manage-roles": "/boundary/tutorials/oss-administration/oss-manage-roles", + "boundary/oss-manage-intro": "/boundary/tutorials/oss-administration/oss-manage-intro", + "boundary/oss-getting-started-next-steps": "/boundary/tutorials/oss-getting-started/oss-getting-started-next-steps", + "boundary/oss-getting-started-intro": "/boundary/tutorials/oss-getting-started/oss-getting-started-intro", + "boundary/oss-getting-started-install": "/boundary/tutorials/oss-getting-started/oss-getting-started-install", + "boundary/oss-getting-started-dev": "/boundary/tutorials/oss-getting-started/oss-getting-started-dev", + "boundary/oss-getting-started-desktop-app": "/boundary/tutorials/oss-getting-started/oss-getting-started-desktop-app", + "boundary/oss-getting-started-console": "/boundary/tutorials/oss-getting-started/oss-getting-started-console", + "boundary/oss-getting-started-connect": "/boundary/tutorials/oss-getting-started/oss-getting-started-connect", + "boundary/oss-getting-started-config": "/boundary/tutorials/oss-getting-started/oss-getting-started-config" +} diff --git a/src/layouts/sidebar-sidecar/utils/prepare-nav-data-for-client.ts b/src/layouts/sidebar-sidecar/utils/prepare-nav-data-for-client.ts index a6a6733644..54d282708d 100644 --- a/src/layouts/sidebar-sidecar/utils/prepare-nav-data-for-client.ts +++ b/src/layouts/sidebar-sidecar/utils/prepare-nav-data-for-client.ts @@ -10,7 +10,6 @@ import path from 'path' import { getIsExternalLearnLink, getIsRewriteableDocsLink, - getTutorialMap, rewriteExternalDocsLink, rewriteExternalLearnLink, } from 'lib/remark-plugins/rewrite-tutorial-links/utils' @@ -59,8 +58,6 @@ function isNavDirectLink(value: NavNode): value is NavDirectLink { return value.hasOwnProperty('href') } -let TUTORIAL_MAP - /** * Prepares all sidebar nav items for client-side rendering. Keeps track of the * index of each node using `startingIndex` and the `traversedNodes` property @@ -70,22 +67,23 @@ let TUTORIAL_MAP async function prepareNavDataForClient({ basePaths, nodes, + tutorialMap, startingIndex = 0, }: { basePaths: string[] nodes: NavNode[] + tutorialMap: Record startingIndex?: number }): Promise<{ preparedItems: MenuItem[]; traversedNodes: number }> { const preparedNodes = [] - TUTORIAL_MAP = TUTORIAL_MAP ?? (await getTutorialMap()) - let count = 0 for (let i = 0; i < nodes.length; i++) { const node = nodes[i] const result = await prepareNavNodeForClient({ basePaths, node, + tutorialMap, nodeIndex: startingIndex + count, }) if (result) { @@ -118,10 +116,12 @@ async function prepareNavDataForClient({ async function prepareNavNodeForClient({ basePaths, node, + tutorialMap, nodeIndex, }: { node: NavNode basePaths: string[] + tutorialMap: Record nodeIndex: number }): Promise<{ preparedItem: MenuItem; traversedNodes: number }> { /** @@ -142,6 +142,7 @@ async function prepareNavNodeForClient({ const { preparedItems, traversedNodes } = await prepareNavDataForClient({ basePaths, nodes: node.routes, + tutorialMap, startingIndex: nodeIndex + 1, }) const preparedItem = { @@ -210,7 +211,7 @@ async function prepareNavNodeForClient({ let newHref const urlObject = new URL(node.href) if (getIsExternalLearnLink(node.href)) { - newHref = rewriteExternalLearnLink(urlObject, TUTORIAL_MAP) + newHref = rewriteExternalLearnLink(urlObject, tutorialMap) } else if (getIsRewriteableDocsLink(node.href)) { newHref = rewriteExternalDocsLink(urlObject) } diff --git a/src/lib/learn-client/index.ts b/src/lib/learn-client/index.ts index 3f838bd8da..184cd92e4e 100644 --- a/src/lib/learn-client/index.ts +++ b/src/lib/learn-client/index.ts @@ -2,19 +2,6 @@ * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: MPL-2.0 */ -function getFetch() { - // Note: purposely doing a conditional require here so that `@vercel/fetch` is not included in the client bundle - if (typeof window === 'undefined') { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const createFetch = require('@vercel/fetch') - return createFetch() - } - return window.fetch -} - -// some of our reqs occur in a node env where fetch -// isn't defined e.g. algolia search script -const fetch = getFetch() export function get(path: string, token?: string) { const options: RequestInit = { diff --git a/src/lib/remark-plugins/rewrite-tutorial-links/__tests__/rewrite-tutorial-links.test.ts b/src/lib/remark-plugins/rewrite-tutorial-links/__tests__/rewrite-tutorial-links.test.ts index d572dc6dd2..b13cea9677 100644 --- a/src/lib/remark-plugins/rewrite-tutorial-links/__tests__/rewrite-tutorial-links.test.ts +++ b/src/lib/remark-plugins/rewrite-tutorial-links/__tests__/rewrite-tutorial-links.test.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: MPL-2.0 */ -import nock from 'nock' import remark from 'remark' import { rewriteTutorialLinksPlugin } from 'lib/remark-plugins/rewrite-tutorial-links' import { productSlugs, productSlugsToHostNames } from 'lib/products' @@ -73,7 +72,6 @@ const TEST_MD_LINKS = { } /** - * Mocks return value from 'api/tutorials-map' endpoint * When adding new MD_LINK tests, make sure the path is accounted for below * * [key: database tutorial slug]: value — dev dot absolute path @@ -93,21 +91,13 @@ const MOCK_TUTORIALS_MAP = { // TESTS ----------------------------------------------------------------- describe('rewriteTutorialLinks remark plugin', () => { - beforeEach(async () => { - // the api base url defaults to localhost when no VERCEL_URL is provided - const scope = nock('http://localhost:3000/api/tutorials-map') - .persist() - .get(/.*/) - .reply(200, MOCK_TUTORIALS_MAP) - }) - test('Only internal Learn links are rewritten', async () => { const contentsWithoutPlugin = await remark().process( TEST_MD_LINKS.nonLearnLink ) const contentsWithPlugin = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.nonLearnLink) expect(String(contentsWithPlugin)).toEqual(String(contentsWithoutPlugin)) @@ -118,7 +108,7 @@ describe('rewriteTutorialLinks remark plugin', () => { const contentsWithoutPlugin = await remark().process(input) const contentsWithPlugin = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(input) expect(String(contentsWithPlugin)).toEqual(String(contentsWithoutPlugin)) @@ -133,7 +123,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test.each(testInputs)('%s', async (testInput: string) => { const contentsWithoutPlugin = await remark().process(testInput) const contentsWithPlugin = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(testInput) expect(String(contentsWithPlugin)).toEqual(String(contentsWithoutPlugin)) }) @@ -141,7 +131,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test("Local anchor links aren't rewritten", async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.plainAnchor) const path = isolatePathFromMarkdown(String(contents)) @@ -150,7 +140,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Product tutorial links are rewritten to dev portal paths', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productTutorial) const result = String(contents) @@ -161,7 +151,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Product collection links are rewritten to dev portal paths', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productCollection) const result = String(contents) @@ -171,7 +161,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Product external collection links are rewritten to relative dev portal paths', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productExternalCollection) const result = String(contents) @@ -181,11 +171,11 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Anchor links are rewritten properly', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productTutorialAnchorLink) const externalLinkContents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.externalAnchorLink) const path = isolatePathFromMarkdown(String(contents)) @@ -203,7 +193,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Query params are rewritten properly', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productTutorialQueryParam) const queryParamCollectionSlug = /get-started-kubernetes/ @@ -213,7 +203,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Query params with an anchor link are rewritten properly', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productTutorialQueryParamWithAnchor) const queryParamSlugWithAnchor = new RegExp(`get-started-nomad/${slug}#`) @@ -224,14 +214,14 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Incorrect link does not throw, only logs the error message', async () => { const getContents = async () => await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.errorLink) expect(getContents).not.toThrowError() }) test('Definition link is rewritten', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productDefintionLink) const path = String(contents).split(':')[1].trim() @@ -240,7 +230,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Search page on learn is made external', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.searchPage) expect(String(contents)).toMatch(/(learn.hashicorp.com)?\/search/) @@ -248,11 +238,11 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Product hub pages should be rewritten to dev portal', async () => { const interalLinkContents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productHubLink) const internalPath = isolatePathFromMarkdown(String(interalLinkContents)) const externalLinkContents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productHubExternalLink) const externalPath = isolatePathFromMarkdown(String(externalLinkContents)) const productHub = /^\/vault\/tutorials$/ @@ -263,10 +253,10 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Product docs links are rewritten to dev portal', async () => { const docsLinkContents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productDocsLink) const pluginLinkContents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productPluginsLink) const pluginLinkPath = isolatePathFromMarkdown(String(pluginLinkContents)) @@ -278,7 +268,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Product api links are rewritten to api-docs', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productDocsApiLink) const path = isolatePathFromMarkdown(String(contents)) @@ -287,7 +277,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Product docs link .html reference should be removed', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productDocsApiLinkWithHtml) const path = isolatePathFromMarkdown(String(contents)) @@ -298,11 +288,11 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Product docs link with anchor are rewritten properly', async () => { const basicAnchorContents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productDocsAnchorLink) const anchorWithHtmlContents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productDocsLinkAnchorWithHtml) const basicAnchorPath = isolatePathFromMarkdown(String(basicAnchorContents)) @@ -318,14 +308,14 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Product /trial path is not rewritten', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productDocsLinkNonDoc) expect(String(contents)).toMatch(TEST_MD_LINKS.productDocsLinkNonDoc) }) test('Product usecase path is not rewritten', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.productDocsLinkUseCases) expect(String(contents)).toMatch(TEST_MD_LINKS.productDocsLinkUseCases) @@ -333,7 +323,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Waf link should be rewritten properly', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.wafTutorialLink) const newPath = isolatePathFromMarkdown(String(contents)) @@ -344,7 +334,7 @@ describe('rewriteTutorialLinks remark plugin', () => { test('Onboarding link should be rewritten properly', async () => { const contents = await remark() - .use(rewriteTutorialLinksPlugin) + .use(rewriteTutorialLinksPlugin, { tutorialMap: MOCK_TUTORIALS_MAP }) .process(TEST_MD_LINKS.onboardingCollectionLink) const newPath = isolatePathFromMarkdown(String(contents)) diff --git a/src/lib/remark-plugins/rewrite-tutorial-links/index.ts b/src/lib/remark-plugins/rewrite-tutorial-links/index.ts index 5f1fd6f4f0..ba38d882db 100644 --- a/src/lib/remark-plugins/rewrite-tutorial-links/index.ts +++ b/src/lib/remark-plugins/rewrite-tutorial-links/index.ts @@ -27,22 +27,22 @@ import { Plugin } from 'unified' import { visit } from 'unist-util-visit' import { RewriteTutorialLinksPluginOptions } from './types' import { DEFAULT_CONTENT_TYPE } from './constants' -import { getTutorialMap } from './utils' import { rewriteTutorialsLink } from './utils/rewrite-tutorials-link' -let TUTORIAL_MAP - export const rewriteTutorialLinksPlugin: Plugin = ( options: RewriteTutorialLinksPluginOptions = {} ) => { const { contentType = DEFAULT_CONTENT_TYPE, tutorialMap } = options return async function transformer(tree) { - // Load the tutorial map if it's not provided - TUTORIAL_MAP = tutorialMap ?? (await getTutorialMap()) + // Throw an error if the tutorial map is not provided. Due to how + // Remark plugins are typed, we can't have required parameters. + if (!tutorialMap) { + throw new Error('[rewriteTutorialLinksPlugin] tutorialMap is required') + } // Visit link and defintion node types visit(tree, ['link', 'definition'], (node: Link | Definition) => { - node.url = rewriteTutorialsLink(node.url, TUTORIAL_MAP, contentType) + node.url = rewriteTutorialsLink(node.url, tutorialMap, contentType) }) } } diff --git a/src/lib/remark-plugins/rewrite-tutorial-links/utils/get-tutorial-map.ts b/src/lib/remark-plugins/rewrite-tutorial-links/utils/get-tutorial-map.ts deleted file mode 100644 index f793148d08..0000000000 --- a/src/lib/remark-plugins/rewrite-tutorial-links/utils/get-tutorial-map.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import fetch from 'node-fetch' -import moize, { Options } from 'moize' -import { generateTutorialMap } from 'pages/api/tutorials-map' - -export async function getTutorialMap() { - let result = {} - const isDuringBuild = process.env.VERCEL && process.env.CI - const baseUrl = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` - : 'http://localhost:3000' - const apiRoute = new URL('api/tutorials-map', baseUrl) - - /** - * For statically generated pages, we can cache the tutorial - * map in memory. For ISR pages, we call an api route (/api/tutorials-map) - * where the map is cached. This ensures that the expensive map - * generation function is not called for every single tutorial - */ - try { - if (isDuringBuild) { - const tutorialMapRes = await cachedGenerateTutorialMap() - result = tutorialMapRes - } else { - const tutorialMapRes = await fetch(apiRoute) - if (tutorialMapRes.ok) { - result = await tutorialMapRes.json() - } - } - } catch (e) { - console.error(e, 'Tutorials map could not be generated') - } - - return result -} - -// Caching the return value in memory for static builds to limit api calls -const moizeOpts: Options = { isPromise: true, maxSize: Infinity } -const cachedGenerateTutorialMap = moize(generateTutorialMap, moizeOpts) diff --git a/src/lib/remark-plugins/rewrite-tutorial-links/utils/index.ts b/src/lib/remark-plugins/rewrite-tutorial-links/utils/index.ts index 99fbacbad8..d952d2dfb3 100644 --- a/src/lib/remark-plugins/rewrite-tutorial-links/utils/index.ts +++ b/src/lib/remark-plugins/rewrite-tutorial-links/utils/index.ts @@ -8,7 +8,6 @@ export { getIsExternalLearnPath, } from './get-is-external-learn-link' export { getIsRewriteableDocsLink } from './get-is-rewriteable-docs-link' -export { getTutorialMap } from './get-tutorial-map' export { rewriteExternalCollectionLink } from './rewrite-external-collection-link' export { rewriteExternalLearnLink } from './rewrite-external-learn-link' export { rewriteExternalTutorialLink } from './rewrite-external-tutorial-link' diff --git a/src/lib/sitemap/tutorials-content-fields.ts b/src/lib/sitemap/tutorials-content-fields.ts index 0cbb6c6103..97f9e4bbbc 100644 --- a/src/lib/sitemap/tutorials-content-fields.ts +++ b/src/lib/sitemap/tutorials-content-fields.ts @@ -6,7 +6,7 @@ import { getAllCollections } from 'lib/learn-client/api/collection' import { SectionOption } from 'lib/learn-client/types' import { activeProductSlugs } from 'lib/products' -import { generateTutorialMap } from 'pages/api/tutorials-map' +import tutorialMap from 'data/_tutorial-map.generated.json' import { ProductSlug } from 'types/products' import { getCollectionSlug } from 'views/collection-view/helpers' import { makeSitemapField } from './helpers' @@ -38,7 +38,7 @@ async function getCollectionPaths() { export async function allTutorialsFields() { const landingSlugs = getTutorialLandingPaths() const collectionSlugs = await getCollectionPaths() - const tutorialSlugs = Object.values(await generateTutorialMap()) + const tutorialSlugs = Object.values(tutorialMap) return [...landingSlugs, ...collectionSlugs, ...tutorialSlugs].map( (slug: string) => makeSitemapField({ slug }) ) diff --git a/src/pages/api/tutorials-map.ts b/src/pages/api/tutorials-map.ts deleted file mode 100644 index 1527484104..0000000000 --- a/src/pages/api/tutorials-map.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import { NextApiRequest, NextApiResponse } from 'next' -import { StatusCodes } from 'http-status-codes' -import { getTutorialSlug } from 'views/collection-view/helpers' -import { getAllTutorials } from 'lib/learn-client/api/tutorial' - -// 1 hour -const MAP_MAX_AGE_IN_SECONDS = 60 * 60 * 60 - -/** - * This API caches a tutorial-map blob for the tutorial rewrites - * remark plugin - lib/remark-plugins/rewrite-tutorial-links. - * This ensures that calls to `getAllTutorials` are limited - * for ISR generated tutorial views - */ -export default async function tutorialsMapHandler( - req: NextApiRequest, - res: NextApiResponse -) { - try { - const mapData = await generateTutorialMap() - if (Object.keys(mapData).length > 0) { - res.setHeader('cache-control', `s-maxage=${MAP_MAX_AGE_IN_SECONDS}`) - res.status(StatusCodes.OK).json(mapData) - } else { - res - .status(StatusCodes.BAD_REQUEST) - .json({ message: 'Failed to generate tutorial map' }) - } - } catch (e) { - res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json({ message: 'Server error: unable to generate tutorial map' }) - } -} - -/** - * This function creates a map of 'database-slug': 'dev-dot/path' - */ -export async function generateTutorialMap() { - const allTutorials = await getAllTutorials({ - fullContent: false, - slugsOnly: true, - }) - - const mapItems = allTutorials.map((t) => { - const oldPath = t.slug - const newPath = getTutorialSlug(t.slug, t.collection_slug) - return [oldPath, newPath] - }) - - return Object.fromEntries(mapItems) -} diff --git a/src/views/docs-view/server.ts b/src/views/docs-view/server.ts index 278a9238bc..51ad18d38e 100644 --- a/src/views/docs-view/server.ts +++ b/src/views/docs-view/server.ts @@ -33,6 +33,7 @@ import { generateProductLandingSidebarNavData, generateTopLevelSidebarNavData, } from 'components/sidebar/helpers' +import tutorialMap from 'data/_tutorial-map.generated.json' // Local imports import { getProductUrlAdjuster } from './utils/product-url-adjusters' @@ -191,7 +192,7 @@ export function getStaticGenerationFunctions< * `rewriteTutorialLinksPlugin` does not rewrite links like * `/waypoint` to `/waypoint/tutorials`. */ - [rewriteTutorialLinksPlugin, { contentType: 'docs' }], + [rewriteTutorialLinksPlugin, { contentType: 'docs', tutorialMap }], /** * Rewrite docs content links, which are authored without prefix. * For example, in Waypoint docs authors write "/docs/some-thing", @@ -299,6 +300,7 @@ export function getStaticGenerationFunctions< await prepareNavDataForClient({ basePaths: [product.slug, basePath], nodes: navData, + tutorialMap, }) /** diff --git a/src/views/product-tutorials-view/helpers/__tests__/detect-and-reformat-learn-url.test.ts b/src/views/product-tutorials-view/helpers/__tests__/detect-and-reformat-learn-url.test.ts index fa8152d526..fdd698cac0 100644 --- a/src/views/product-tutorials-view/helpers/__tests__/detect-and-reformat-learn-url.test.ts +++ b/src/views/product-tutorials-view/helpers/__tests__/detect-and-reformat-learn-url.test.ts @@ -3,34 +3,30 @@ * SPDX-License-Identifier: MPL-2.0 */ -import nock from 'nock' import detectAndReformatLearnUrl from '../detect-and-reformat-learn-url' /** - * Mocks return value from 'api/tutorials-map' endpoint. + * Mocks return value from generated map JSON file. * Maps tutorial slugs to tutorial URLs which include the default collection. * When adding new test cases, make sure the path is accounted for below * * [key: database tutorial slug]: value — dev dot absolute path */ -const MOCK_TUTORIALS_MAP = { - 'consul/gossip-encryption-secure': - '/consul/tutorials/gossip-encryption-secure', - 'waypoint/aws-ecs': '/waypoint/tutorials/deploy-aws/aws-ecs', - 'vault/getting-started-install': - '/vault/tutorials/getting-started/getting-started-install', - 'cloud/amazon-peering-hcp': '/hcp/tutorials/networking/amazon-peering-hcp', -} +vi.mock('data/_tutorial-map.generated.json', async () => { + return { + default: { + 'consul/gossip-encryption-secure': + '/consul/tutorials/gossip-encryption-secure', + 'waypoint/aws-ecs': '/waypoint/tutorials/deploy-aws/aws-ecs', + 'vault/getting-started-install': + '/vault/tutorials/getting-started/getting-started-install', + 'cloud/amazon-peering-hcp': + '/hcp/tutorials/networking/amazon-peering-hcp', + }, + } +}) describe('detectAndReformatLearnUrl', () => { - beforeEach(async () => { - // the api base url defaults to localhost when no VERCEL_URL is provided - const scope = nock('http://localhost:3000/api/tutorials-map') - .persist() - .get(/.*/) - .reply(200, MOCK_TUTORIALS_MAP) - }) - it('returns .io URLs with unsupported base paths unmodified', async () => { const nonDocsIoUrls: string[] = [ 'https://cloud.hashicorp.com/pricing', diff --git a/src/views/product-tutorials-view/helpers/detect-and-reformat-learn-url.ts b/src/views/product-tutorials-view/helpers/detect-and-reformat-learn-url.ts index 0368adf9f3..6a052f9cf1 100644 --- a/src/views/product-tutorials-view/helpers/detect-and-reformat-learn-url.ts +++ b/src/views/product-tutorials-view/helpers/detect-and-reformat-learn-url.ts @@ -3,20 +3,16 @@ * SPDX-License-Identifier: MPL-2.0 */ -import { getTutorialMap } from 'lib/remark-plugins/rewrite-tutorial-links/utils' +import tutorialMap from 'data/_tutorial-map.generated.json' import { rewriteTutorialsLink } from 'lib/remark-plugins/rewrite-tutorial-links/utils/rewrite-tutorials-link' -let TUTORIAL_MAP - /** * Given a URL string, if it is a Learn URL that can be rewritten, reformat the * URL to work with dev-dot's URL structure. Otherwise, return the URL * unmodified. */ async function detectAndReformatLearnUrl(url: string): Promise { - TUTORIAL_MAP = await getTutorialMap() - - return rewriteTutorialsLink(url, TUTORIAL_MAP) ?? url + return rewriteTutorialsLink(url, tutorialMap) ?? url } export default detectAndReformatLearnUrl