Creating and Editing Pages

This page shows how to create, test, and maintain Istio documentation topics.

Before you begin

Before you can work on Istio documentation, you first need to create a fork of the Istio documentation repository as described in Working with GitHub.

Choosing a page type

As you prepare to write a new topic, think about which of these page types is the best fit for your content:

ConceptA concept page explains some significant aspect of Istio. For example, a concept page might describe the Mixer's configuration model and explain some of its subtleties. Typically, concept pages don't include sequences of steps, but instead provide links to tasks that do.
ReferenceA reference page provides exhaustive lists of things like API parameters, command-line options, configuration settings, and procedures.
ExamplesAn example page describes a fully working stand-alone example highlighting a particular set of features. Examples must have easy to follow setup and usage instructions so users can quickly run the sample themselves and experiment with changing the example to explore the system. Examples are tested and maintained for technical accuracy.
TaskA task page shows how to do a single thing, typically by giving a short sequence of steps. Task pages have minimal explanation, but often provide links to conceptual topics that provide related background and knowledge. Tasks are tested and maintained for technical accuracy.
SetupA setup page is similar to a task page, except that it is focused on installation activities. Setup pages are tested and maintained for technical accuracy.
Blog PostA blog post is a timely article on Istio or products and technologies related to it. Typically, posts fall in one of the following four categories:
  • Posts detailing the author’s experience using and configuring Istio, especially those that articulate a novel experience or perspective.
  • Posts highlighting or announcing Istio features.
  • Posts announcing an Istio-related event.
  • Posts detailing how to accomplish a task or fulfill a specific use case using Istio. Unlike Tasks and Examples, the technical accuracy of blog posts is not maintained and tested after publication.
FAQA FAQ entry is for quick answers to common customer questions. An answer doesn't normally introduce any new concept and is instead narrowly focused on some practical bit of advice or insight, with links back into the main documentation for the user to learn more.
Ops GuideFor practical solutions to address specific problems encountered while running Istio in a real-world setting.

Naming a topic

Choose a title for your topic that has the keywords you want search engines to find. Create a filename for your topic that uses the words in your title, separated by hyphens, all in lower case.

Updating front matter

Every documentation file needs to start with front matter. The front matter is a block of YAML that is between triple-dashed lines at the top of each file. Here’s the chunk of front matter you should start with:

---
title: <title>
description: <description>
weight: <weight>
keywords: [keyword1,keyword2,...]
---

Copy the above at the start of your new markdown file and update the information fields. The available front matter fields are:

FieldDescription
titleThe short title of the page
linktitleAn alternate, typically shorter, title for the page which is used in the side bar to reference the page
subtitleAn optional subtitle which gets displayed below the main title
descriptionA one-line description of what the page is about
iconAn optional path to an image file which gets displayed next to the main title
weightAn integer used to determine the sort order of this page relative to other pages in the same directory
keywordsAn array of keywords describing the page, used to create the web of See Also links
draftWhen true, prevents the page from showing up in any navigation area
aliasesSee Renaming, moving, or deleting pages below for details on this item
skip_tocSet this to true to prevent the page from having a table of contents generated for it
skip_bylineSet this to true to prevent the page from having a byline under the main title
skip_seealsoSet this to true to prevent the page from having a “See also” section generated for it
force_inline_tocSet this to true to force the generated table of contents to be inserted inline in the text instead of in a sidebar
simple_listSet this to true to force a generated section page to use a simple list layout rather that a gallery layout
content_aboveSet this to true to force the content portion of a section index to be rendered above the auto-generated content

There are a few more front matter fields available specifically for blog posts:

FieldDescription
publishdateDate of the post’s original publication
last_updateDate when the post last received a major revision
attributionOptional name of the post’s author
twitterOptional Twitter handle of the post’s author

Adding images

Put image files in the same directory as your markdown file. The preferred image format is SVG. Within markdown, use the following sequence to add the image:

{{< image width="75%" ratio="45.34%"
    link="./myfile.svg"
    alt="Alternate text to display when the image can't be loaded"
    title="A tooltip displayed when hovering over the image"
    caption="A caption displayed under the image"
    >}}

The link and caption values are required, all other values are optional.

If the title value isn’t supplied, it’ll default to the same as caption. If the alt value is not supplied, it’ll default to title or if that’s not defined, to caption.

width represents the percentage of space used by the image relative to the surrounding text. If the value is not specified, it defaults to 100%.

ratio represents the ratio of the image height compared to the image width. This value is calculated automatically for any local image content, but must be calculated manually when referencing external image content. In that case, ratio should be set to (image height / image width) * 100.

Adding icons

You can embed some common icons in your content using:

{{< warning_icon >}}
{{< idea_icon >}}
{{< checkmark_icon >}}
{{< cancel_icon >}}
{{< tip_icon >}}

which look like , , , and .

Linking to other pages

There are three types of links that can be included in documentation. Each uses a different way to indicate the link target:

  1. Internet Link. You use classic URL syntax, preferably with the HTTPS protocol, to reference files on the Internet:

    [see here](https://mysite/myfile.html)
    
  2. Relative Link. You use relative links that start with a period to reference any content that is at the same level as the current file, or below within the hierarchy of the site:

    [see here](./adir/anotherfile.html)
    
  3. Absolute Link. You use absolute links that start with a / to reference content outside of the current hierarchy:

    [see here](/docs/adir/afile/)
    

GitHub

There are a few ways to reference files from GitHub:

  • {{< github_file >}} is how you reference individual files in GitHub such as yaml files. This produces a link to https://raw.githubusercontent.com/istio/istio*

    [liveness]({{< github_file >}}/samples/health-check/liveness-command.yaml)
    
  • {{< github_tree >}} is how you reference a directory tree in GitHub. This produces a link to https://github.com/istio/istio/tree*

    [httpbin]({{< github_tree >}}/samples/httpbin)
    
  • {{< github_blob >}} is how you reference a file in GitHub sources. This produces a link to https://github.com/istio/istio/blob*

    [RawVM MySQL]({{< github_blob >}}/samples/rawvm/README.md)
    

The above annotations yield links to the appropriate branch in GitHub, relative to the branch that the documentation is currently targeting. If you need to manually construct a URL, you can use the sequence {{< source_branch_name >}} to get the name of the currently targeted branch.

Version information

You can obtain the current Istio version described by the web site using either of {{< istio_version >}} or {{< istio_full_version >}} which render as 1.2 and 1.2.0 respectively.

{{< source_branch_name >}} gets expanded to the name of the branch of the istio/istio GitHub repository that the web site is targeting. This renders as release-1.1.

Embedding preformatted blocks

You can embed blocks of preformatted content using the text sequence:

{{< text plain >}}
func HelloWorld() {
  fmt.Println("Hello World")
}
{{< /text >}}

The above produces this kind of output:

func HelloWorld() {
  fmt.Println("Hello World")
}

You must indicate the syntax of the content in the preformatted block. Above, the block was marked as being plain indicating that no syntax coloring should be applied to the block. Consider the same block, but now annotated with the Go language syntax:

{{< text go >}}
func HelloWorld() {
  fmt.Println("Hello World")
}
{{< /text >}}

which renders as:

func HelloWorld() {
  fmt.Println("Hello World")
}

Supported syntax are plain, markdown, yaml, json, java, javascript, c, cpp, csharp, go, html, protobuf, perl, docker, and bash.

Command-lines

When showing one or more bash command-lines, you start each command-line with a $:

{{< text bash >}}
$ echo "Hello"
{{< /text >}}

which produces:

$ echo "Hello"

You can have as many command-lines as you want, but only one chunk of output is recognized.

{{< text bash >}}
$ echo "Hello" >file.txt
$ cat file.txt
Hello
{{< /text >}}

which yields:

$ echo "Hello" >file.txt
$ cat file.txt
Hello

You can also use line continuation in your command-lines:

{{< text bash >}}
$ echo "Hello" \
    >file.txt
$ echo "There" >>file.txt
$ cat file.txt
Hello
There
{{< /text >}}

which looks like:

$ echo "Hello" \
    >file.txt
$ echo "There" >>file.txt
$ cat file.txt
Hello
There

By default, the output section is handled using the plain syntax. If the output uses a well-known syntax, you can specify it and get proper coloring for it. This is particularly common for YAML or JSON output:

{{< text bash json >}}
$ kubectl -n istio-system logs $(kubectl -n istio-system get pods -l istio-mixer-type=telemetry -o jsonpath='{.items[0].metadata.name}') mixer | grep \"instance\":\"newlog.logentry.istio-system\"
{"level":"warn","ts":"2017-09-21T04:33:31.249Z","instance":"newlog.logentry.istio-system","destination":"details","latency":"6.848ms","responseCode":200,"responseSize":178,"source":"productpage","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.291Z","instance":"newlog.logentry.istio-system","destination":"ratings","latency":"6.753ms","responseCode":200,"responseSize":48,"source":"reviews","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.263Z","instance":"newlog.logentry.istio-system","destination":"reviews","latency":"39.848ms","responseCode":200,"responseSize":379,"source":"productpage","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.239Z","instance":"newlog.logentry.istio-system","destination":"productpage","latency":"67.675ms","responseCode":200,"responseSize":5599,"source":"ingress.istio-system.svc.cluster.local","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.233Z","instance":"newlog.logentry.istio-system","destination":"ingress.istio-system.svc.cluster.local","latency":"74.47ms","responseCode":200,"responseSize":5599,"source":"unknown","user":"unknown"}
{{< /text >}}

which gives:

$ kubectl -n istio-system logs $(kubectl -n istio-system get pods -l istio-mixer-type=telemetry -o jsonpath='{.items[0].metadata.name}') mixer | grep \"instance\":\"newlog.logentry.istio-system\"
{"level":"warn","ts":"2017-09-21T04:33:31.249Z","instance":"newlog.logentry.istio-system","destination":"details","latency":"6.848ms","responseCode":200,"responseSize":178,"source":"productpage","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.291Z","instance":"newlog.logentry.istio-system","destination":"ratings","latency":"6.753ms","responseCode":200,"responseSize":48,"source":"reviews","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.263Z","instance":"newlog.logentry.istio-system","destination":"reviews","latency":"39.848ms","responseCode":200,"responseSize":379,"source":"productpage","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.239Z","instance":"newlog.logentry.istio-system","destination":"productpage","latency":"67.675ms","responseCode":200,"responseSize":5599,"source":"ingress.istio-system.svc.cluster.local","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.233Z","instance":"newlog.logentry.istio-system","destination":"ingress.istio-system.svc.cluster.local","latency":"74.47ms","responseCode":200,"responseSize":5599,"source":"unknown","user":"unknown"}

Expanded form

To use the more advanced features for preformatted content which are described in the following sections, you must use the extended form of the text sequence rather than the simplified form shown so far. The expanded form uses normal HTML attributes:

{{< text syntax="bash" outputis="json" >}}
$ kubectl -n istio-system logs $(kubectl -n istio-system get pods -l istio-mixer-type=telemetry -o jsonpath='{.items[0].metadata.name}') mixer | grep \"instance\":\"newlog.logentry.istio-system\"
{"level":"warn","ts":"2017-09-21T04:33:31.249Z","instance":"newlog.logentry.istio-system","destination":"details","latency":"6.848ms","responseCode":200,"responseSize":178,"source":"productpage","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.291Z","instance":"newlog.logentry.istio-system","destination":"ratings","latency":"6.753ms","responseCode":200,"responseSize":48,"source":"reviews","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.263Z","instance":"newlog.logentry.istio-system","destination":"reviews","latency":"39.848ms","responseCode":200,"responseSize":379,"source":"productpage","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.239Z","instance":"newlog.logentry.istio-system","destination":"productpage","latency":"67.675ms","responseCode":200,"responseSize":5599,"source":"ingress.istio-system.svc.cluster.local","user":"unknown"}
{"level":"warn","ts":"2017-09-21T04:33:31.233Z","instance":"newlog.logentry.istio-system","destination":"ingress.istio-system.svc.cluster.local","latency":"74.47ms","responseCode":200,"responseSize":5599,"source":"unknown","user":"unknown"}
{{< /text >}}

The available attributes are:

AttributeDescription
fileThe path of a file to show in the preformatted block.
urlThe URL of a document to show in the preformatted block.
syntaxThe syntax of the preformatted block.
outputisWhen the syntax is bash, this specifies the command output’s syntax.
downloadasThe default file name used when the user downloads the preformatted block.
expandlinksWhether or not to expand GitHub file references in the preformatted block.
snippetThe name of the snippet of content to extract from the preformatted block.

Inline vs. imported content

So far, you’ve seen examples of inline preformatted content but it’s also possible to import content, either from a file in the documentation repository or from an arbitrary URL on the Internet. For this, you use the text_import sequence.

You can use text_import with the file attribute to reference a file within the documentation repository:

{{< text_import file="test/snippet_example.txt" syntax="plain" >}}

which renders as:

BEFORE

# $snippet SNIP1
This is chunk 1
on two lines
# $endsnippet

# $snippet SNIP2
This is chunk 2
# $endsnippet

# $snippet SNIP1
This is chunk 3
# $endsnippet

AFTER

You can dynamically pull in content from the Internet in a similar way, but using the url attribute instead of the file attribute. Here’s the same file, but retrieved from a URL dynamically rather than being baked into the HTML statically:

{{< text_import url="https://raw.githubusercontent.com/istio/istio.io/master/test/snippet_example.txt" syntax="plain" >}}

which produces the following result:

Zip

If the file is from a different origin site, CORS should be enabled on that site. Note that the GitHub raw content site (raw.githubusercontent.com) may be used here.

Download name

You can control the name that the browser uses when the user chooses to download the preformatted content by using the downloadas attribute. For example:

{{< text syntax="go" downloadas="hello.go" >}}
func HelloWorld() {
  fmt.Println("Hello World")
}
{{< /text >}}

If you don’t specify a download name, then it is derived automatically based on the title of the current page for inline content, or from the name of the file or URL for imported content.

If your preformatted content references a file from Istio’s GitHub repository, you can surround the relative path name of the file with a pair of @ symbols. These indicate that the path should be rendered as a link to the file from the current branch in GitHub. For example:

Zip
{{< text bash >}}
$ kubectl apply -f @samples/bookinfo/networking/virtual-service-reviews-v3.yaml@
{{< /text >}}

Which renders as:

Zip
$ kubectl apply -f @samples/bookinfo/networking/virtual-service-reviews-v3.yaml@

If your preformatted content happens to use @ symbols for something else, you can turn off link expansion using the expandlinks attribute:

Zip
{{< text syntax="bash" expandlinks="false" >}}
$ kubectl apply -f @samples/bookinfo/networking/virtual-service-reviews-v3.yaml@
{{< /text >}}

Snippets

When using imported content, you can control which parts of the content to render using named snippets, which represent portions of a file. You declare snippets in a file using the $snippets annotation with a paired $endsnippet annotation. The content between the two annotations represents the snippet. For example, you could have a text file that looks like this:

BEFORE

# $snippet SNIP1
This is chunk 1
on two lines
# $endsnippet

# $snippet SNIP2
This is chunk 2
# $endsnippet

# $snippet SNIP1
This is chunk 3
# $endsnippet

AFTER

and in your markdown file, you can then reference a particular snippet with the snippet attribute such as:

{{< text_import file="test/snippet_example.txt" syntax="plain" snippet="SNIP1" >}}

which renders as:

This is chunk 1
on two lines

Glossary terms

When first introducing a specialized Istio term in a page, it is desirable to annotate the terms as being in the glossary. This will produce special rendering inviting the user to click on the term in order to get a pop-up with the definition.

Mixer uses {{<gloss>}}adapters{{</gloss>}} to interface to backends.

which looks like:

Mixer uses adapters to interface to backends.

If the term displayed on the page doesn’t exactly match the entry in the glossary, you can specify a substitution:

Mixer uses an {{<gloss adapters>}}adapter{{</gloss>}} to interface to a backend.

which looks like:

Mixer uses an adapter to interface to a backend.

So even though the glossary entry is for adapters, the singular form of adapter can be used in the text.

Callouts

You can bring special attention to blocks of content by highlighting warnings, ideas, tips, and quotes:

{{< warning >}}
This is an important warning
{{< /warning >}}

{{< idea >}}
This is a great idea
{{< /idea >}}

{{< tip >}}
This is a useful tip from an expert
{{< /tip >}}

{{< quote >}}
This is a quote from somewhere
{{< /quote >}}

which looks like:

Embedding boilerplate text

You can embed common boilerplate text into any markdown output using the boilerplate sequence:

{{< boilerplate example >}}

which results in:

This is some boilerplate markdown text.

A sample nested text block.

You supply the name of a boilerplate file to insert at the current location. Available boilerplates are located in the boilerplates directory. Boilerplates are just normal markdown files.

Using tabs

If you have some content to display in a variety of formats, it is convenient to use a tab set and display each format in a different tab. To insert tabbed content, you use a combination of tabset and tabs annotations:

{{< tabset cookie-name="platform" >}}

{{< tab name="One" cookie-value="one" >}}
ONE
{{< /tab >}}

{{< tab name="Two" cookie-value="two" >}}
TWO
{{< /tab >}}

{{< tab name="Three" cookie-value="three" >}}
THREE
{{< /tab >}}

{{< /tabset >}}

which produces the following output:

ONE

The name attribute of each tab contains the text to display for the tab. The content of the tab can be almost any normal markdown.

The optional cookie-name and cookie-value attributes allow the tab setting to be sticky across visits to the page. As the user selects a tab, the cookie will be automatically saved with the given name and value. If multiple tab sets use the same cookie name and values, their setting will be automatically synchronized across pages. This is particularly useful when there are many tab sets in the site that hold the same types of formats.

For example, if many tab sets are used to represent a choice between GCP, BlueMix and AWS, they can all use a cookie name of environment and values of gcp, bluemix, and aws. When a user selects a tab in one page, the equivalent tab will automatically be selected in any other tab set.

Limitations

You can use almost any markdown in a tab, except for the following:

  • No headers. Headers in a tab will appear in the table of contents and yet clicking on the entry in the table of contents will not automatically select the tab.

  • No nested tab sets. Don’t try it, it’s horrible.

Renaming, moving, or deleting pages

If you move pages around or delete them completely, you should make sure existing links users may have to those pages continue to work. You do this by adding aliases which will cause the user to be redirected automatically from the old URL to a new URL.

In the page that is the target of the redirect (where you’d like users to land), you simply add the following to the front-matter:

aliases:
    - <path>

For example

---
title: Frequently Asked Questions
description: Questions Asked Frequently.
weight: 12
aliases:
    - /faq
---

With the above in a page saved as help/faq.md, the user will be able to access the page by going to istio.io/help/faq/ as normal, as well as istio.io/faq/.

You can also add many redirects like so:

---
title: Frequently Asked Questions
description: Questions Asked Frequently.
weight: 12
aliases:
    - /faq
    - /faq2
    - /faq3
---

Building and testing the site

Once you’ve edited some content files, you’ll want to build the site in order to test your changes. We use Hugo to generate our sites. To build and test the site locally, we use a Docker image that contains Hugo. To build and serve the site, simply go to the root of the tree and do:

$ make serve

This will build the site and start a web server hosting the site. You can then connect to the web server at http://localhost:1313.

To make and serve the site from a remote server, override ISTIO_SERVE_DOMAIN as follows with the IP address or DNS Domain of the server as follows:

$ make ISTIO_SERVE_DOMAIN=192.168.7.105 serve

This will build the site and start a web server hosting the site. You can then connect to the web server at http://192.168.7.105:1313.

All English content for the site is located in the content directory, as well as in sibling translated directories such as content_zh.

Linting

We use linters to ensure some base quality to the site’s content. These linters must run without complaining before you can submit your changes into the repository. The linters check:

  • HTML proofing, which ensures all your links are valid along with other checks.

  • Spell checking.

  • Style checking, which makes sure your markdown files comply with our common style rules.

You can run these linters locally using:

$ make lint

If you get spelling errors, you have three choices to address each:

  • It’s a real typo, so fix your markdown.

  • It’s a command/field/symbol name, so stick some backticks around it.

  • It’s really valid, so go add the word to the .spelling file which is at the root of the repo.

If you’re having trouble with the link checker due to poor Internet connectivity, you can set any value to an environment variable named INTERNAL_ONLY to prevent the linter from checking external links:

$ make INTERNAL_ONLY=True lint