Generate test snippets from md files POC (#7044)

* Generate test snippets from md POC

* fixes

* fix python lint

* fix snip source

* improvements

* update snip calls

* add copyright header

* lint errors

* lint error in md instructions

* better default snip_id

* run test without snippet gen

* gofmt

* fixes

* fix path

* fixes

* fix verify
This commit is contained in:
Frank Budinsky 2020-04-14 10:13:44 -04:00 committed by GitHub
parent 83dbc79145
commit 3b59501872
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 390 additions and 20 deletions

View File

@ -5,6 +5,7 @@ weight: 40
keywords: [security,authentication,migration]
aliases:
- /docs/tasks/security/mtls-migration/
test: true
---
This task shows how to ensure your workloads only communicate using mutual TLS as they are migrated to
@ -55,7 +56,7 @@ the policies to enforce STRICT mutual TLS between the workloads.
* Verify setup by sending an http request (using curl command) from any sleep pod (among those in namespace `foo`, `bar` or `legacy`) to `httpbin.foo`. All requests should success with HTTP code 200.
{{< text bash >}}
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.bar to httpbin.foo: 200
@ -96,7 +97,7 @@ EOF
Now, you should see the request from `sleep.legacy` to `httpbin.foo` failing.
{{< text bash >}}
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.bar to httpbin.foo: 200
@ -110,7 +111,7 @@ If you installed Istio with `values.global.proxy.privileged=true`, you can use `
traffic is encrypted or not.
{{< text bash >}}
$ kubectl exec -nfoo $(kubectl get pod -nfoo -lapp=httpbin -ojsonpath={.items..metadata.name}) -c istio-proxy -it -- sudo tcpdump dst port 80 -A
$ kubectl exec -nfoo "$(kubectl get pod -nfoo -lapp=httpbin -ojsonpath={.items..metadata.name})" -c istio-proxy -it -- sudo tcpdump dst port 80 -A
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
{{< /text >}}
@ -140,7 +141,7 @@ Now, both the `foo` and `bar` namespaces enforce mutual TLS only traffic, so you
failing for both.
{{< text bash >}}
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
{{< /text >}}
## Clean up the example

View File

@ -0,0 +1,132 @@
#!/bin/bash
# Copyright Istio Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
####################################################################################################
# WARNING: THIS IS AN AUTO-GENERATED FILE, DO NOT EDIT. PLEASE MODIFY THE ORIGINAL MARKDOWN FILE:
# docs/tasks/security/authentication/mtls-migration/index.md
####################################################################################################
snip_set_up_the_cluster_1() {
kubectl create ns foo
kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
kubectl create ns bar
kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n bar
kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n bar
}
snip_set_up_the_cluster_2() {
kubectl create ns legacy
kubectl apply -f samples/sleep/sleep.yaml -n legacy
}
snip_set_up_the_cluster_3() {
for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
}
# shellcheck disable=SC2034
! read -r -d '' snip_set_up_the_cluster_3_out <<ENDSNIP
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.legacy to httpbin.foo: 200
sleep.legacy to httpbin.bar: 200
ENDSNIP
snip_set_up_the_cluster_4() {
kubectl get peerauthentication --all-namespaces
}
# shellcheck disable=SC2034
! read -r -d '' snip_set_up_the_cluster_4_out <<ENDSNIP
No resources found
ENDSNIP
snip_set_up_the_cluster_5() {
kubectl get destinationrule --all-namespaces
}
# shellcheck disable=SC2034
! read -r -d '' snip_set_up_the_cluster_5_out <<ENDSNIP
No resources found
ENDSNIP
snip_lock_down_to_mutual_tls_by_namespace_1() {
kubectl apply -n foo -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default"
spec:
mtls:
mode: STRICT
EOF
}
snip_lock_down_to_mutual_tls_by_namespace_2() {
for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
}
# shellcheck disable=SC2034
! read -r -d '' snip_lock_down_to_mutual_tls_by_namespace_2_out <<ENDSNIP
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 200
ENDSNIP
snip_lock_down_to_mutual_tls_by_namespace_3() {
kubectl exec -nfoo "$(kubectl get pod -nfoo -lapp=httpbin -ojsonpath={.items..metadata.name})" -c istio-proxy -it -- sudo tcpdump dst port 80 -A
}
# shellcheck disable=SC2034
! read -r -d '' snip_lock_down_to_mutual_tls_by_namespace_3_out <<ENDSNIP
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
ENDSNIP
snip_lock_down_mutual_tls_for_the_entire_mesh_1() {
kubectl apply -n istio-system -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default"
spec:
mtls:
mode: STRICT
EOF
}
snip_lock_down_mutual_tls_for_the_entire_mesh_2() {
for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
}
snip_clean_up_the_example_1() {
kubectl delete peerauthentication --all-namespaces --all
}
snip_clean_up_the_example_2() {
kubectl delete ns foo bar legacy
}
# shellcheck disable=SC2034
! read -r -d '' snip_clean_up_the_example_2_out <<ENDSNIP
Namespaces foo bar legacy deleted.
ENDSNIP

View File

@ -5,6 +5,7 @@ weight: 30
keywords: [traffic-management,traffic-shifting]
aliases:
- /docs/tasks/traffic-management/version-migration.html
test: true
---
This task shows you how to gradually migrate traffic from one version of a
@ -35,7 +36,7 @@ If you haven't already applied destination rules, follow the instructions in [Ap
1. To get started, run this command to route all traffic to the `v1` version of
each microservice.
{{< text bash >}}
{{< text syntax=bash snip_id=config_all_v1 >}}
$ kubectl apply -f @samples/bookinfo/networking/virtual-service-all-v1.yaml@
{{< /text >}}
@ -49,7 +50,7 @@ the [Bookinfo](/docs/examples/bookinfo/#determine-the-ingress-ip-and-port) doc.
1. Transfer 50% of the traffic from `reviews:v1` to `reviews:v3` with the following command:
{{< text bash >}}
{{< text syntax=bash snip_id=config_50_v3 >}}
$ kubectl apply -f @samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml@
{{< /text >}}
@ -57,7 +58,7 @@ the [Bookinfo](/docs/examples/bookinfo/#determine-the-ingress-ip-and-port) doc.
1. Confirm the rule was replaced:
{{< text bash yaml >}}
{{< text syntax=bash outputis=yaml snip_id=verify_config_50_v3 >}}
$ kubectl get virtualservice reviews -o yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
@ -92,7 +93,7 @@ the star ratings service, but the `v1` version does not.
1. Assuming you decide that the `reviews:v3` microservice is stable, you can
route 100% of the traffic to `reviews:v3` by applying this virtual service:
{{< text bash >}}
{{< text syntax=bash snip_id=config_100_v3 >}}
$ kubectl apply -f @samples/bookinfo/networking/virtual-service-reviews-v3.yaml@
{{< /text >}}
@ -112,7 +113,7 @@ article [Canary Deployments using Istio](/blog/2017/0.1-canary/).
1. Remove the application routing rules:
{{< text bash >}}
{{< text syntax=bash snip_id=cleanup >}}
$ kubectl delete -f @samples/bookinfo/networking/virtual-service-all-v1.yaml@
{{< /text >}}

View File

@ -0,0 +1,62 @@
#!/bin/bash
# Copyright Istio Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
####################################################################################################
# WARNING: THIS IS AN AUTO-GENERATED FILE, DO NOT EDIT. PLEASE MODIFY THE ORIGINAL MARKDOWN FILE:
# docs/tasks/traffic-management/traffic-shifting/index.md
####################################################################################################
snip_config_all_v1() {
kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml
}
snip_config_50_v3() {
kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml
}
snip_verify_config_50_v3() {
kubectl get virtualservice reviews -o yaml
}
# shellcheck disable=SC2034
! read -r -d '' snip_verify_config_50_v3_out <<ENDSNIP
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
...
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 50
- destination:
host: reviews
subset: v3
weight: 50
ENDSNIP
snip_config_100_v3() {
kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-v3.yaml
}
snip_cleanup() {
kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml
}

View File

@ -319,10 +319,15 @@ func (s Script) runCommand(ctx Context) {
// Copy the commands from the snippet.
for _, snippetCommand := range snippetCommands {
commandLines = append(commandLines, filterCommandLine(snippetCommand))
if sinfo.name != "" {
snippetCommand = filterCommandLine(snippetCommand)
}
commandLines = append(commandLines, snippetCommand)
}
} else {
// Not a snippet, just copy the line directly to the command.
// TODO commandLines = append(commandLines, line) // Commands outside of snippets should be proper bash
// TODO Need to fix some tests that are incorrectly annotating commands outside of snippets.
commandLines = append(commandLines, filterCommandLine(line))
}
}
@ -390,6 +395,11 @@ func (s Script) createSnippets(ctx Context) {
// Verify the output for this snippet.
sinfo.verify()
if strings.HasPrefix(sinfo.name, "_NOGEN_") {
// No snippet to generate, just verifying output.
continue
}
// Verify the output, if configured to do so.
snippetOutput := ""
if sinfo.outputIs != "" {
@ -530,7 +540,8 @@ func parseSnippet(ctx Context, lineIndex *int, lines []string) snippetInfo {
}
if info.name == "" {
ctx.Fatalf("snippet missing name")
// If no snippet name is set, the framework will run the commands/verifiers without generating snippets.
info.name = fmt.Sprintf("_NOGEN_%d", *lineIndex)
}
if info.outputIs == "" && info.outputSnippet {

19
scripts/gen_snips.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# Copyright Istio Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -e
find content/en -name '*.md' -exec grep --quiet 'test: true' {} \; -exec python scripts/snip.py {} \;

138
scripts/snip.py Normal file
View File

@ -0,0 +1,138 @@
#!/usr/bin/python
# Copyright Istio Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import re
import os
linenum = 0
snipnum = 0
section = ""
current_snip = None
multiline_cmd = False
output_started = False
snippets = []
HEADER = """#!/bin/bash
# Copyright Istio Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
####################################################################################################
# WARNING: THIS IS AN AUTO-GENERATED FILE, DO NOT EDIT. PLEASE MODIFY THE ORIGINAL MARKDOWN FILE:
# %s
####################################################################################################
"""
startsnip = re.compile(r"^(\s*){{< text (syntax=)?\"?(\w+)\"? .*>}}$")
snippetid = re.compile(r"snip_id=(\w+)")
githubfile = re.compile(r"^([^@]*)@([\w\.\-_/]+)@([^@]*)$")
sectionhead = re.compile(r"^##+ (.*)$")
invalidchar = re.compile(r"[^0-9a-zA-Z_]")
if len(sys.argv) < 2:
print("usage: python snip.py mdfile [ snipdir ]")
sys.exit(1)
markdown = sys.argv[1]
if len(sys.argv) > 2:
snipdir = sys.argv[2]
else:
snipdir = os.path.dirname(markdown)
snipfile = "snips.sh" if markdown.split('/')[-1] == "index.md" else markdown.split('/')[-1] + "_snips.sh"
print("generating snips: " + os.path.join(snipdir, snipfile))
with open(markdown, 'rt') as mdfile:
for line in mdfile:
linenum += 1
match = sectionhead.match(line)
if match:
snipnum = 0
section = invalidchar.sub('', match.group(1).replace(" ", "_")).lower()
continue
match = startsnip.match(line)
if match:
snipnum += 1
indent = match.group(1)
kind = match.group(3)
match = snippetid.search(line)
if match:
id = "snip_" + match.group(1)
else:
id = "snip_%s_%d" % (section, snipnum)
if kind == "bash":
script = "\n%s() {\n" % id
else:
script = "\n# shellcheck disable=SC2034\n! read -r -d '' %s <<ENDSNIP\n" % id
current_snip = {"start": linenum, "id": id, "kind": kind, "indent": indent, "script": ["", script]}
snippets.append(current_snip)
continue
if current_snip != None:
if current_snip["indent"]:
_, line = line.split(current_snip["indent"], 1)
if "{{< /text >}}" in line:
if current_snip["kind"] == "bash" and not output_started:
script = "}\n"
else:
script = "ENDSNIP\n"
current_snip["script"].append(script)
current_snip = None
multiline_cmd = False
output_started = False
else:
if current_snip["kind"] == "bash":
if line.startswith("$ "):
line = line[2:]
else:
if multiline_cmd:
if line == "EOF\n":
multiline_cmd = False
elif not current_snip["script"][-1].endswith("\\\n"):
# command output
if not output_started:
current_snip["script"].append("}\n\n# shellcheck disable=SC2034\n! read -r -d '' %s_out <<ENDSNIP\n" % id)
output_started = True
match = githubfile.match(line)
if match:
line = match.group(1) + match.group(2) + match.group(3)
if "<<EOF" in line:
multiline_cmd = True
current_snip["script"].append(line)
with open(os.path.join(snipdir, snipfile), 'w') as f:
f.write(HEADER % markdown.split("content/en/")[1] if "content/en/" in markdown else markdown)
for snippet in snippets:
lines = snippet["script"]
for line in lines:
f.write(line)

View File

@ -18,6 +18,8 @@ set -e
set -u
set -o pipefail
source ${REPO_ROOT}/content/en/docs/tasks/traffic-management/traffic-shifting/snips.sh
# setup bookinfo & sleep pods
kubectl label namespace default istio-injection=enabled --overwrite || true
@ -78,8 +80,8 @@ function verify_traffic_shift() {
# Step 1
# $snippet route_all_v1 syntax="bash" outputis="text" outputsnippet="true"
$ kubectl apply -f @samples/bookinfo/networking/virtual-service-all-v1.yaml@
# $snippet
snip_config_all_v1
# $verify
virtualservice.networking.istio.io/productpage created
virtualservice.networking.istio.io/reviews created
@ -92,29 +94,33 @@ verify_traffic_shift 0
# Step 3: switch 50% traffic to v3
# $snippet route_50_percent_v3 syntax="bash" outputis="text" outputsnippet="true"
$ kubectl apply -f @samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml@
# $snippet
snip_config_50_v3
# $verify
virtualservice.networking.istio.io/reviews configured
# $endsnippet
istioctl experimental wait --for=distribution VirtualService reviews.default
istioctl experimental wait --for=distribution VirtualService reviews.default
# Step 4: Confirm the rule was replaced
# $snippet verify_review_virtualservice syntax="bash" outputis="text" outputsnippet="true"
$ kubectl get virtualservice reviews -o yaml
# $snippet
snip_verify_config_50_v3
# $verify verifier="contains"
subset: v3
# $endsnippet
#TODO The above verify could instead test the doc output snippet if we add a suitable yaml verifier
#TODO # $verify verifier="yaml-subset"
#TODO echo "$verify_config_50_v3_out"
# Step 5: verify rating stars visible 50% of the time
verify_traffic_shift 50
# Step 6: route 100% traffic to v3
# $snippet route_100_percent_v3 syntax="bash" outputis="text" outputsnippet="true"
$ kubectl apply -f @samples/bookinfo/networking/virtual-service-reviews-v3.yaml@
# $snippet
snip_config_100_v3
# $endsnippet
verify_traffic_shift 100