Skip to content

Commit

Permalink
Validating config in docs (envoyproxy#11394)
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitri Dolguikh <[email protected]>
  • Loading branch information
Dmitri Dolguikh authored Jul 9, 2020
1 parent 36c3b10 commit a1d3f4b
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 20 deletions.
6 changes: 6 additions & 0 deletions api/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ The documentation can be built locally in the root of https://github.com/envoypr
docs/build.sh
```

To skip configuration examples validation:

```
SPHINX_SKIP_CONFIG_VALIDATION=true docs/build.sh
```

Or to use a hermetic Docker container:

```
Expand Down
8 changes: 7 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
./docs/build.sh
```

The output can be found in `generated/docs`.
The output can be found in `generated/docs`. By default configuration examples are going to be validated during build.
To disable validation, set `SPHINX_SKIP_CONFIG_VALIDATION` environment variable to `true`:

```bash
SPHINX_SKIP_CONFIG_VALIDATION=true docs/build.sh
```


# How the Envoy website and docs are updated

Expand Down
62 changes: 62 additions & 0 deletions docs/_ext/validating_code_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from typing import List
from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.parsers.rst import directives
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective
from sphinx.directives.code import CodeBlock
from sphinx.errors import ExtensionError

import os
import subprocess


class ValidatingCodeBlock(CodeBlock):
"""A directive that provides protobuf yaml formatting and validation.
'type-name' option is required and expected to conain full Envoy API type.
An ExtensionError is raised on validation failure.
Validation will be skipped if SPHINX_SKIP_CONFIG_VALIDATION environment variable is set.
"""
has_content = True
required_arguments = CodeBlock.required_arguments
optional_arguments = CodeBlock.optional_arguments
final_argument_whitespace = CodeBlock.final_argument_whitespace
option_spec = {
'type-name': directives.unchanged,
}
option_spec.update(CodeBlock.option_spec)
skip_validation = (os.getenv('SPHINX_SKIP_CONFIG_VALIDATION') or 'false').lower() == 'true'

def run(self):
source, line = self.state_machine.get_source_and_line(self.lineno)
# built-in directives.unchanged_required option validator produces a confusing error message
if self.options.get('type-name') == None:
raise ExtensionError("Expected type name in: {0} line: {1}".format(source, line))

if not ValidatingCodeBlock.skip_validation:
args = [
'bazel-bin/tools/config_validation/validate_fragment',
self.options.get('type-name'), '-s', '\n'.join(self.content)
]
completed = subprocess.run(args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding='utf-8')
if completed.returncode != 0:
raise ExtensionError(
"Failed config validation for type: '{0}' in: {1} line: {2}:\n {3}".format(
self.options.get('type-name'), source, line, completed.stderr))

self.options.pop('type-name', None)
return list(CodeBlock.run(self))


def setup(app):
app.add_directive("validated-code-block", ValidatingCodeBlock)

return {
'version': '0.1',
'parallel_read_safe': True,
'parallel_write_safe': True,
}
8 changes: 7 additions & 1 deletion docs/build.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/usr/bin/env bash

# set SPHINX_SKIP_CONFIG_VALIDATION environment variable to true to skip
# validation of configuration examples

. tools/shell_utils.sh

set -e
Expand Down Expand Up @@ -126,6 +129,9 @@ cp -f "${CONFIGS_DIR}"/google-vrp/envoy-edge.yaml "${GENERATED_RST_DIR}"/configu

rsync -rav $API_DIR/diagrams "${GENERATED_RST_DIR}/api-docs"

rsync -av "${SCRIPT_DIR}"/root/ "${SCRIPT_DIR}"/conf.py "${GENERATED_RST_DIR}"
rsync -av "${SCRIPT_DIR}"/root/ "${SCRIPT_DIR}"/conf.py "${SCRIPT_DIR}"/_ext "${GENERATED_RST_DIR}"

# To speed up validate_fragment invocations in validating_code_block
bazel build ${BAZEL_BUILD_OPTIONS} //tools/config_validation:validate_fragment

sphinx-build -W --keep-going -b html "${GENERATED_RST_DIR}" "${DOCS_OUTPUT_DIR}"
8 changes: 7 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,13 @@ def setup(app):
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinxcontrib.httpdomain', 'sphinx.ext.extlinks', 'sphinx.ext.ifconfig']

sys.path.append(os.path.abspath("./_ext"))

extensions = [
'sphinxcontrib.httpdomain', 'sphinx.ext.extlinks', 'sphinx.ext.ifconfig',
'validating_code_block'
]
extlinks = {
'repo': ('https://github.com/envoyproxy/envoy/blob/{}/%s'.format(blob_sha), ''),
'api': ('https://github.com/envoyproxy/envoy/blob/{}/api/%s'.format(blob_sha), ''),
Expand Down
3 changes: 2 additions & 1 deletion docs/root/configuration/operations/runtime.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ runtime <envoy_v3_api_msg_config.bootstrap.v3.LayeredRuntime>` bootstrap configu
layering. Runtime settings in later layers override earlier layers. A typical configuration might
be:

.. code-block:: yaml
.. validated-code-block:: yaml
:type-name: envoy.config.bootstrap.v3.LayeredRuntime

layers:
- name: static_layer_0
Expand Down
29 changes: 16 additions & 13 deletions docs/root/configuration/overview/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Static

A minimal fully static bootstrap config is provided below:

.. code-block:: yaml
.. validated-code-block:: yaml
:type-name: envoy.config.bootstrap.v3.Bootstrap

admin:
access_log_path: /tmp/admin_access.log
Expand Down Expand Up @@ -61,7 +62,8 @@ discovery <arch_overview_dynamic_config_eds>` via an
:ref:`EDS<envoy_v3_api_file_envoy/service/endpoint/v3/eds.proto>` gRPC management server listening
on 127.0.0.1:5678 is provided below:

.. code-block:: yaml
.. validated-code-block:: yaml
:type-name: envoy.config.bootstrap.v3.Bootstrap

admin:
access_log_path: /tmp/admin_access.log
Expand Down Expand Up @@ -100,8 +102,8 @@ on 127.0.0.1:5678 is provided below:
api_config_source:
api_type: GRPC
grpc_services:
envoy_grpc:
cluster_name: xds_cluster
- envoy_grpc:
cluster_name: xds_cluster
- name: xds_cluster
connect_timeout: 0.25s
type: STATIC
Expand Down Expand Up @@ -159,7 +161,8 @@ A fully dynamic bootstrap configuration, in which all resources other than
those belonging to the management server are discovered via xDS is provided
below:

.. code-block:: yaml
.. validated-code-block:: yaml
:type-name: envoy.config.bootstrap.v3.Bootstrap

admin:
access_log_path: /tmp/admin_access.log
Expand All @@ -171,14 +174,14 @@ below:
api_config_source:
api_type: GRPC
grpc_services:
envoy_grpc:
cluster_name: xds_cluster
- envoy_grpc:
cluster_name: xds_cluster
cds_config:
api_config_source:
api_type: GRPC
grpc_services:
envoy_grpc:
cluster_name: xds_cluster
- envoy_grpc:
cluster_name: xds_cluster

static_resources:
clusters:
Expand Down Expand Up @@ -226,8 +229,8 @@ The management server could respond to LDS requests with:
api_config_source:
api_type: GRPC
grpc_services:
envoy_grpc:
cluster_name: xds_cluster
- envoy_grpc:
cluster_name: xds_cluster
http_filters:
- name: envoy.filters.http.router
Expand Down Expand Up @@ -262,8 +265,8 @@ The management server could respond to CDS requests with:
api_config_source:
api_type: GRPC
grpc_services:
envoy_grpc:
cluster_name: xds_cluster
- envoy_grpc:
cluster_name: xds_cluster
The management server could respond to EDS requests with:

Expand Down
22 changes: 19 additions & 3 deletions tools/config_validation/validate_fragment.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

from bazel_tools.tools.python.runfiles import runfiles

import argparse


def ValidateFragment(type_name, fragment):
"""Validate a dictionary representing a JSON/YAML fragment against an Envoy API proto3 type.
Expand Down Expand Up @@ -50,7 +52,21 @@ def ValidateFragment(type_name, fragment):
json_format.Parse(json_fragment, msg, descriptor_pool=pool)


def ParseArgs():
parser = argparse.ArgumentParser(
description='Validate a YAML fragment against an Envoy API proto3 type.')
parser.add_argument(
'message_type',
help='a string providing the type name, e.g. envoy.config.bootstrap.v3.Bootstrap.')
parser.add_argument('fragment_path', nargs='?', help='Path to a YAML configuration fragment.')
parser.add_argument('-s', required=False, help='YAML configuration fragment.')

return parser.parse_args()


if __name__ == '__main__':
type_name, yaml_path = sys.argv[1:]
ValidateFragment(type_name, yaml.load(pathlib.Path(yaml_path).read_text(),
Loader=yaml.FullLoader))
parsed_args = ParseArgs()
message_type = parsed_args.message_type
content = parsed_args.s if (parsed_args.fragment_path is None) else pathlib.Path(
parsed_args.fragment_path).read_text()
ValidateFragment(message_type, yaml.load(content, Loader=yaml.FullLoader))

0 comments on commit a1d3f4b

Please sign in to comment.