From 7d6d704e888ab2d3c7924edcb29de1b6ca99afd2 Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Fri, 16 Jun 2023 21:53:35 +0100 Subject: [PATCH] [v9.5.x] Update make-docs procedure (#70274) Update make-docs procedure (#70265) ## 4.1.0 (2023-06-16) ### Added - Mounts of `layouts` and `config` directories for the `website` project. Ensures that local changes to mounts or shortcodes are reflected in the development server. ### Fixed - Version inference for versioned docs pages. Pages in versioned projects now have the `versioned: true` front matter set to ensure that "version" in $.Page.Scratch is set on builds. ## 4.0.0 (2023-06-06) ### Removed - `doc-validator/%` target. The behavior of the target was not as described. Instead, to limit `doc-validator` to only specific files, refer to https://grafana.com/docs/writers-toolkit/writing-guide/tooling-and-workflows/validate-technical-documentation/#run-on-specific-files. ## 3.0.0 (2023-05-18) ### Fixed - Compatibility with the updated Make targets in the `website` repository. `docs` now runs this script itself, `server-docs` builds the site with the `docs` Hugo environment. (cherry picked from commit a4a16b62c7a1ccb8d8d6679c318846e9472f634d) Signed-off-by: Jack Baldry --- docs/Makefile | 39 +--- docs/README.md | 11 +- docs/docs.mk | 117 ++++++++++++ docs/make-docs | 449 ++++++++++++++++++++++++++++++++++++++++++++++ docs/variables.mk | 11 ++ 5 files changed, 592 insertions(+), 35 deletions(-) create mode 100644 docs/docs.mk create mode 100755 docs/make-docs create mode 100644 docs/variables.mk diff --git a/docs/Makefile b/docs/Makefile index 1d069a90692..2eaab546cf3 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,33 +1,8 @@ -.PHONY: pull docs docs-quick docs-no-pull docs-test docs-local-static +.ONESHELL: +.DELETE_ON_ERROR: +export SHELL := bash +export SHELLOPTS := pipefail:errexit +MAKEFLAGS += --warn-undefined-variables +MAKEFLAGS += --no-builtin-rule -PODMAN = $(shell if command -v podman >/dev/null 2>&1; then echo podman; else echo docker; fi) -IMAGE = grafana/docs-base:latest -CONTENT_PATH = /hugo/content/docs/grafana/latest -LOCAL_STATIC_PATH = ../../website/static -PORT = 3002:3002 - -pull: - $(PODMAN) pull $(IMAGE) - -docs: pull - $(PODMAN) run --init -v $(shell pwd)/sources:$(CONTENT_PATH):Z -p $(PORT) --rm -it $(IMAGE) make server - -docs-preview: pull - $(PODMAN) run --init -v $(shell pwd)/sources:$(CONTENT_PATH):Z -p $(PORT) --rm -it $(IMAGE) make server BUILD_DRAFTS=true - -docs-no-pull: - $(PODMAN) run --init -v $(shell pwd)/sources:$(CONTENT_PATH):Z -p $(PORT) --rm -it $(IMAGE) make server - -docs-test: pull - $(PODMAN) run --init -v $(shell pwd)/sources:$(CONTENT_PATH):Z --rm -it $(IMAGE) make prod - -# expects that you have grafana/website checked out in same path as the grafana repo. -docs-local-static: pull - if [ ! -d "$(LOCAL_STATIC_PATH)" ]; then echo "local path (website project) $(LOCAL_STATIC_PATH) not found"]; exit 1; fi - $(PODMAN) run --init -v $(shell pwd)/sources:$(CONTENT_PATH):Z \ - -v $(shell pwd)/$(LOCAL_STATIC_PATH):/hugo/static:Z -p $(PORT) --rm -it $(IMAGE) - -.PHONY: doc-validator/% -doc-validator/%: ## Run doc-validator on a specific path. To lint the path /docs/sources/administration, run 'make doc-validator/administration'. -doc-validator/%: - $(PODMAN) run --init -v "$(shell pwd)/sources:/sources" grafana/doc-validator:latest --skip-image-validation --include=$(subst doc-validator/,,$@) ./sources /docs/grafana/latest +include docs.mk diff --git a/docs/README.md b/docs/README.md index 22fa25f6f78..006bb41449b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,14 +1,19 @@ # Building the docs locally -When you contribute to documentation, it is a good practice to build the docs on your local machine to make sure your changes appear as you expect. This README explains the process for doing that. +When you contribute to documentation, it's a good practice to build the docs on your local machine to make sure your changes appear as you expect. This README explains the process for doing that. + +To build a local version, you need to run a process in a Docker container. +Grafana periodically updates the Docker image, [`docs-base`](https://hub.docker.com/r/grafana/docs-base), to update the styling of the Docs. ## Requirements -Docker >= 2.1.0.3 -Yarn >= 1.22.4 +- Docker >= 2.1.0.3 +- Yarn >= 1.22.4 ## Build the doc site +First, make sure the Docker daemon is running on your machine. Then, follow these steps: + 1. On the command line, first change to the docs folder: `cd docs`. 1. Run `make docs`. This launches a preview of the website with the current grafana docs at `http://localhost:3002/docs/grafana/latest/` which will refresh automatically when changes are made to content in the `sources` directory. diff --git a/docs/docs.mk b/docs/docs.mk new file mode 100644 index 00000000000..4c729c57d7c --- /dev/null +++ b/docs/docs.mk @@ -0,0 +1,117 @@ +# The source of this file is https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/docs.mk. +# 4.0.0 (2023-06-06) +include variables.mk +-include variables.mk.local + +.ONESHELL: +.DELETE_ON_ERROR: +export SHELL := bash +export SHELLOPTS := pipefail:errexit +MAKEFLAGS += --warn-undefined-variables +MAKEFLAGS += --no-builtin-rule + +.DEFAULT_GOAL: help + +# Adapted from https://www.thapaliya.com/en/writings/well-documented-makefiles/ +.PHONY: help +help: ## Display this help. +help: + @awk 'BEGIN { \ + FS = ": ##"; \ + printf "Usage:\n make \n\nTargets:\n" \ + } \ + /^[a-zA-Z0-9_\.\-\/%]+: ##/ { printf " %-15s %s\n", $$1, $$2 }' \ + $(MAKEFILE_LIST) + +GIT_ROOT := $(shell git rev-parse --show-toplevel) + +PODMAN := $(shell if command -v podman >/dev/null 2>&1; then echo podman; else echo docker; fi) + +ifeq ($(PROJECTS),) +$(error "PROJECTS variable must be defined in variables.mk") +endif + +# First project is considered the primary one used for doc-validator. +PRIMARY_PROJECT := $(subst /,-,$(firstword $(subst :, ,$(firstword $(PROJECTS))))) + +# Name for the container. +ifeq ($(origin DOCS_CONTAINER), undefined) +export DOCS_CONTAINER := $(PRIMARY_PROJECT)-docs +endif + +# Host port to publish container port to. +ifeq ($(origin DOCS_HOST_PORT), undefined) +export DOCS_HOST_PORT := 3002 +endif + +# Container image used to perform Hugo build. +ifeq ($(origin DOCS_IMAGE), undefined) +export DOCS_IMAGE := grafana/docs-base:latest +endif + +# Container image used for doc-validator linting. +ifeq ($(origin DOC_VALIDATOR_IMAGE), undefined) +export DOC_VALIDATOR_IMAGE := grafana/doc-validator:latest +endif + +# Container image used for vale linting. +ifeq ($(origin VALE_IMAGE), undefined) +export VALE_IMAGE := grafana/vale:latest +endif + +# PATH-like list of directories within which to find projects. +# If all projects are checked out into the same directory, ~/repos/ for example, then the default should work. +ifeq ($(origin REPOS_PATH), undefined) +export REPOS_PATH := $(realpath $(GIT_ROOT)/..) +endif + +# How to treat Hugo relref errors. +ifeq ($(origin HUGO_REFLINKSERRORLEVEL), undefined) +export HUGO_REFLINKSERRORLEVEL := WARNING +endif + +.PHONY: docs-rm +docs-rm: ## Remove the docs container. + $(PODMAN) rm -f $(DOCS_CONTAINER) + +.PHONY: docs-pull +docs-pull: ## Pull documentation base image. + $(PODMAN) pull $(DOCS_IMAGE) + +make-docs: ## Fetch the latest make-docs script. +make-docs: + if [[ ! -f "$(PWD)/make-docs" ]]; then + echo 'WARN: No make-docs script found in the working directory. Run `make update` to download it.' >&2 + exit 1 + fi + +.PHONY: docs +docs: ## Serve documentation locally, which includes pulling the latest `DOCS_IMAGE` (default: `grafana/docs-base:latest`) container image. See also `docs-no-pull`. +docs: docs-pull make-docs + $(PWD)/make-docs $(PROJECTS) + +.PHONY: docs-no-pull +docs-no-pull: ## Serve documentation locally without pulling the `DOCS_IMAGE` (default: `grafana/docs-base:latest`) container image. +docs-no-pull: make-docs + $(PWD)/make-docs $(PROJECTS) + +.PHONY: docs-debug +docs-debug: ## Run Hugo web server with debugging enabled. TODO: support all SERVER_FLAGS defined in website Makefile. +docs-debug: make-docs + WEBSITE_EXEC='hugo server --bind 0.0.0.0 --port 3002 --debug' $(PWD)/make-docs $(PROJECTS) + +.PHONY: doc-validator +doc-validator: ## Run doc-validator on the entire docs folder. +doc-validator: make-docs + DOCS_IMAGE=$(DOC_VALIDATOR_IMAGE) $(PWD)/make-docs $(PROJECTS) + +.PHONY: vale +vale: ## Run vale on the entire docs folder. +vale: make-docs + DOCS_IMAGE=$(VALE_IMAGE) $(PWD)/make-docs $(PROJECTS) + +.PHONY: update +update: ## Fetch the latest version of this Makefile and the `make-docs` script from Writers' Toolkit. + curl -s -LO https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/docs.mk + curl -s -LO https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/make-docs + chmod +x make-docs diff --git a/docs/make-docs b/docs/make-docs new file mode 100755 index 00000000000..3942bf483c3 --- /dev/null +++ b/docs/make-docs @@ -0,0 +1,449 @@ +#!/bin/sh +# The source of this file is https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/make-docs. +# 4.1.0 (2023-06-16) + +set -ef + +readonly DOCS_CONTAINER="${DOCS_CONTAINER:-make-docs}" +readonly DOCS_HOST_PORT="${DOCS_HOST_PORT:-3002}" +readonly DOCS_IMAGE="${DOCS_IMAGE:-grafana/docs-base:latest}" + +readonly DOC_VALIDATOR_INCLUDE="${DOC_VALIDATOR_INCLUDE:-.+\.md$}" +readonly DOC_VALIDATOR_SKIP_CHECKS="${DOC_VALIDATOR_SKIP_CHECKS:-^image-}" + +readonly HUGO_REFLINKSERRORLEVEL="${HUGO_REFLINKSERRORLEVEL:-WARNING}" +readonly VALE_MINALERTLEVEL="${VALE_MINALERTLEVEL:-error}" +readonly WEBSITE_EXEC="${WEBSITE_EXEC:-make server-docs}" +# If set, the docs-base image will run a prebuild script that sets up Hugo mounts. +readonly WEBSITE_MOUNTS="${WEBSITE_MOUNTS:-}" + +PODMAN="$(if command -v podman >/dev/null 2>&1; then echo podman; else echo docker; fi)" + +about() { + cat <...]> $0 [[:[:[:]]]...] + +Examples: + REPOS_PATH=~/ext/grafana/ $0 writers-toolkit tempo:latest helm-charts/mimir-distributed:latest:mimir:docs/sources/mimir-distributed +EOF +} + +if [ $# -lt 1 ]; then + cat <&2 +ERRR: arguments required but not supplied. + +$(about) + +$(usage) +EOF + exit 1 +fi + +readonly REPOS_PATH="${REPOS_PATH:-$(realpath "$(git rev-parse --show-toplevel)/..")}" + +if [ -z "${REPOS_PATH}" ]; then + cat <&2 +ERRR: REPOS_PATH environment variable is required but has not been provided. + +$(usage) +EOF + exit 1 +fi + +SOURCES_as_code='as-code-docs' +SOURCES_enterprise_metrics='backend-enterprise' +SOURCES_enterprise_metrics_='backend-enterprise' +SOURCES_grafana_cloud='website' +SOURCES_grafana_cloud_k6='k6-docs' +SOURCES_grafana_cloud_data_configuration_integrations='cloud-onboarding' +SOURCES_grafana_cloud_frontend_observability_faro_web_sdk='faro-web-sdk' +SOURCES_grafana_cloud_machine_learning='machine-learning' +SOURCES_helm_charts_mimir_distributed='mimir' +SOURCES_helm_charts_tempo_distributed='tempo' +SOURCES_opentelemetry='opentelemetry-docs' + +VERSIONS_as_code='UNVERSIONED' +VERSIONS_grafana_cloud='UNVERSIONED' +VERSIONS_grafana_cloud_k6='UNVERSIONED' +VERSIONS_grafana_cloud_data_configuration_integrations='UNVERSIONED' +VERSIONS_grafana_cloud_frontend_observability_faro_web_sdk='UNVERSIONED' +VERSIONS_grafana_cloud_machine_learning='UNVERSIONED' +VERSIONS_opentelemetry='UNVERSIONED' +VERSIONS_technical_documentation='UNVERSIONED' +VERSIONS_website='UNVERSIONED' +VERSIONS_writers_toolkit='UNVERSIONED' + +PATHS_grafana_cloud='content/docs/grafana-cloud' +PATHS_helm_charts_mimir_distributed='docs/sources/helm-charts/mimir-distributed' +PATHS_helm_charts_tempo_distributed='docs/sources/helm-charts/tempo-distributed' +PATHS_mimir='docs/sources/mimir' +PATHS_tempo='docs/sources/tempo' +PATHS_website='content/docs' + +# identifier STR +# Replace characters that are not valid in an identifier with underscores. +identifier() { + echo "$1" | tr -C '[:alnum:]_\n' '_' +} + +# aget ARRAY KEY +# Get the value of KEY from associative array ARRAY. +# Characters that are not valid in an identifier are replaced with underscores. +aget() { + eval echo '$'"$(identifier "$1")_$(identifier "$2")" +} + +# new_proj populates a new project structure. +new_proj() { + _project="$1" + _version="$2" + _repo="$3" + _path="$4" + + # If version is not set, use the script mapping of project to default versions if it exists. + # Fallback to 'latest'. + if [ -z "${_version}" ]; then + if [ -z "$(aget VERSIONS "${_project}")" ]; then + _version=latest + else + _version="$(aget VERSIONS "${_project}")" + fi + fi + + # If repo is not set, use the script mapping of project to repo name if it exists. + # Fallback to using the project name. + if [ -z "${_repo}" ]; then + if [ -z "$(aget SOURCES "${_project}")" ]; then + _repo="${_project}" + else + _repo="$(aget SOURCES "${_project}")" + fi + fi + + # If path is not set, use the script mapping of project to docs sources path if it exists. + # Fallback to using 'docs/sources'. + if [ -z "${_path}" ]; then + if [ -z "$(aget PATHS "${_project}")" ]; then + _path="docs/sources" + else + _path="$(aget PATHS "${_project}")" + fi + fi + + echo "${_project}:${_version}:${_repo}:${_path}" + unset _project _version _repo _path +} + +# proj_url returns the webserver URL for a project. +# It expects a complete project structure as input. +proj_url() { + IFS=: read -r _project _version _ _ <&2 + echo "NOTE: you must have a checkout of the project '${_repo}' at '${REPOS_PATH##:*}/${_repo}'." >&2 + echo "NOTE: if you have cloned the repository into a directory with a different name, consider changing it to ${_repo}." >&2 + unset _repo + exit 1 +} + +# proj_src returns the host path to content source for a project. +# It expects a complete project structure as input. +# It looks for the provided repository name in each of the paths specified in the REPOS_PATH environment variable. +proj_src() { + IFS=: read -r _ _ _repo _path <&2 + echo "Is '${_src}' the correct source directory?" >&2 + exit 1 + fi + fi + + echo "DEBG: Mounting '${_src}' at container path '${_dst}'" >&2 + if [ -z "${volumes}" ]; then + volumes="--volume=${_src}:${_dst}" + else + volumes="${volumes} --volume=${_src}:${_dst}" + fi + + if [ -n "${_ver}" ] && [ "${_ver}" != 'UNVERSIONED' ]; then + if [ -z "${redirects}" ]; then + redirects="${_dst}^${_ver}" + else + redirects="${redirects} ${_dst}^${_ver}" + fi + fi + unset _url _src _dst _ver +done + +IFS=':' read -r image _ <"${tempfile}" +#!/usr/bin/env bash +for redirect in ${redirects}; do + IFS='^' read -r path ver <<<"\${redirect}" + echo -e "---\\nredirectURL: \"\${path/\/hugo\/content/}\"\\ntype: redirect\\nversioned: true\\n---\\n" > "\${path/\${ver}/_index.md}" +done + +for x in "${url_src_dst_vers}"; do + IFS='^' read -r _ _ dst _ <<<"\${x}" + + while [[ -n "\${dst}" ]]; do + touch "\${dst}/_index.md" + dst="\${dst%/*}" + done +done + +if [[ -n "${WEBSITE_MOUNTS}" ]]; then + unset WEBSITE_SKIP_MOUNTS +fi + +${WEBSITE_EXEC} +EOF + chmod +x "${tempfile}" + volumes="${volumes} --volume=$(realpath "${tempfile}"):/entrypoint" + readonly volumes + + echo + echo "Documentation will be served at the following URLs:" + for x in ${url_src_dst_vers}; do + IFS='^' read -r url _ _ <