Move scripts used in CI to the new location (#11008)

Move scripts used in CI to the new location - **./scripts/ci/**

* Move github scripts

* Move more files

* Move ci scripts and fix dependencies

* Update docs/node-template-release.md

Co-authored-by: João Paulo Silva de Souza <77391175+joao-paulo-parity@users.noreply.github.com>

* Remove Cargo.lock

* Apply suggestions from code review

Co-authored-by: Denis Pisarev <denis.pisarev@parity.io>

* Make more paths uniform

Co-authored-by: João Paulo Silva de Souza <77391175+joao-paulo-parity@users.noreply.github.com>
Co-authored-by: Denis Pisarev <denis.pisarev@parity.io>
This commit is contained in:
Sergejs Kostjucenko
2022-03-14 10:42:34 +02:00
committed by GitHub
parent 42484508d7
commit 46891e849f
25 changed files with 35 additions and 35 deletions
+117
View File
@@ -0,0 +1,117 @@
#!/bin/sh
api_base="https://api.github.com/repos"
# Function to take 2 git tags/commits and get any lines from commit messages
# that contain something that looks like a PR reference: e.g., (#1234)
sanitised_git_logs(){
git --no-pager log --pretty=format:"%s" "$1...$2" |
# Only find messages referencing a PR
grep -E '\(#[0-9]+\)' |
# Strip any asterisks
sed 's/^* //g' |
# And add them all back
sed 's/^/* /g'
}
# Returns the last published release on github
# Note: we can't just use /latest because that ignores prereleases
# repo: 'organization/repo'
# Usage: last_github_release "$repo"
last_github_release(){
i=0
# Iterate over releases until we find the last release that's not just a draft
while [ $i -lt 29 ]; do
out=$(curl -H "Authorization: token $GITHUB_RELEASE_TOKEN" -s "$api_base/$1/releases" | jq ".[$i]")
echo "$out"
# Ugh when echoing to jq, we need to translate newlines into spaces :/
if [ "$(echo "$out" | tr '\r\n' ' ' | jq '.draft')" = "false" ]; then
echo "$out" | tr '\r\n' ' ' | jq '.tag_name'
return
else
i=$((i + 1))
fi
done
}
# Checks whether a tag on github has been verified
# repo: 'organization/repo'
# tagver: 'v1.2.3'
# Usage: check_tag $repo $tagver
check_tag () {
repo=$1
tagver=$2
tag_out=$(curl -H "Authorization: token $GITHUB_RELEASE_TOKEN" -s "$api_base/$repo/git/refs/tags/$tagver")
tag_sha=$(echo "$tag_out" | jq -r .object.sha)
object_url=$(echo "$tag_out" | jq -r .object.url)
if [ "$tag_sha" = "null" ]; then
return 2
fi
verified_str=$(curl -H "Authorization: token $GITHUB_RELEASE_TOKEN" -s "$object_url" | jq -r .verification.verified)
if [ "$verified_str" = "true" ]; then
# Verified, everything is good
return 0
else
# Not verified. Bad juju.
return 1
fi
}
# Checks whether a given PR has a given label.
# repo: 'organization/repo'
# pr_id: 12345
# label: B1-silent
# Usage: has_label $repo $pr_id $label
has_label(){
repo="$1"
pr_id="$2"
label="$3"
# These will exist if the function is called in Gitlab.
# If the function's called in Github, we should have GITHUB_ACCESS_TOKEN set
# already.
if [ -n "$GITHUB_RELEASE_TOKEN" ]; then
GITHUB_TOKEN="$GITHUB_RELEASE_TOKEN"
elif [ -n "$GITHUB_PR_TOKEN" ]; then
GITHUB_TOKEN="$GITHUB_PR_TOKEN"
fi
out=$(curl -H "Authorization: token $GITHUB_TOKEN" -s "$api_base/$repo/pulls/$pr_id")
[ -n "$(echo "$out" | tr -d '\r\n' | jq ".labels | .[] | select(.name==\"$label\")")" ]
}
# Formats a message into a JSON string for posting to Matrix
# message: 'any plaintext message'
# formatted_message: '<strong>optional message formatted in <em>html</em></strong>'
# Usage: structure_message $content $formatted_content (optional)
structure_message() {
if [ -z "$2" ]; then
body=$(jq -Rs --arg body "$1" '{"msgtype": "m.text", $body}' < /dev/null)
else
body=$(jq -Rs --arg body "$1" --arg formatted_body "$2" '{"msgtype": "m.text", $body, "format": "org.matrix.custom.html", $formatted_body}' < /dev/null)
fi
echo "$body"
}
# Post a message to a matrix room
# body: '{body: "JSON string produced by structure_message"}'
# room_id: !fsfSRjgjBWEWffws:matrix.parity.io
# access_token: see https://matrix.org/docs/guides/client-server-api/
# Usage: send_message $body (json formatted) $room_id $access_token
send_message() {
curl -XPOST -d "$1" "https://matrix.parity.io/_matrix/client/r0/rooms/$2/send/m.room.message?access_token=$3"
}
# Check for runtime changes between two commits. This is defined as any changes
# to bin/node/src/runtime, frame/ and primitives/sr_* trees.
has_runtime_changes() {
from=$1
to=$2
if git diff --name-only "${from}...${to}" \
| grep -q -e '^frame/' -e '^primitives/'
then
return 0
else
return 1
fi
}
+193
View File
@@ -0,0 +1,193 @@
# This template contains all of the possible sections and their default values
# Note that all fields that take a lint level have these possible values:
# * deny - An error will be produced and the check will fail
# * warn - A warning will be produced, but the check will not fail
# * allow - No warning or error will be produced, though in some cases a note
# will be
# The values provided in this template are the default values that will be used
# when any section or field is not specified in your own configuration
# If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`.
# This means, if a particular package is only ever used as a target specific
# dependency, such as, for example, the `nix` crate only being used via the
# `target_family = "unix"` configuration, that only having windows targets in
# this list would mean the nix crate, as well as any of its exclusive
# dependencies not shared by any other crates, would be ignored, as the target
# list here is effectively saying which targets you are building for.
targets = [
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#{ triple = "x86_64-unknown-linux-musl" },
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
]
# This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories]
# The path where the advisory database is cloned/fetched into
db-path = "~/.cargo/advisory-db"
# The url of the advisory database to use
db-url = "https://github.com/rustsec/advisory-db"
# The lint level for security vulnerabilities
vulnerability = "deny"
# The lint level for unmaintained crates
unmaintained = "warn"
# The lint level for crates that have been yanked from their source registry
yanked = "warn"
# The lint level for crates with security notices. Note that as of
# 2019-12-17 there are no security notice advisories in
# https://github.com/rustsec/advisory-db
notice = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
#"RUSTSEC-0000-0000",
]
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
# will still output a note when they are encountered.
# * None - CVSS Score 0.0
# * Low - CVSS Score 0.1 - 3.9
# * Medium - CVSS Score 4.0 - 6.9
# * High - CVSS Score 7.0 - 8.9
# * Critical - CVSS Score 9.0 - 10.0
#severity-threshold =
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses]
# The lint level for crates which do not have a detectable license
unlicensed = "deny"
# List of explictly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.7 short identifier (+ optional exception)].
allow = [
#"MIT",
#"Apache-2.0",
#"Apache-2.0 WITH LLVM-exception",
]
# List of explictly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.7 short identifier (+ optional exception)].
deny = [
#"Nokia",
]
# Lint level for licenses considered copyleft
copyleft = "allow"
# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
# * both - The license will be approved if it is both OSI-approved *AND* FSF
# * either - The license will be approved if it is either OSI-approved *OR* FSF
# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
# * neither - This predicate is ignored and the default lint level is used
allow-osi-fsf-free = "either"
# Lint level used when no other predicates are matched
# 1. License isn't in the allow or deny lists
# 2. License isn't copyleft
# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
default = "deny"
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file.
# [possible values: any between 0.0 and 1.0].
confidence-threshold = 0.9
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], name = "adler32", version = "*" },
]
# Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the
# licensing information
[[licenses.clarify]]
# The name of the crate the clarification applies to
name = "ring"
# THe optional version constraint for the crate
#version = "*"
# The SPDX expression for the license requirements of the crate
expression = "OpenSSL"
# One or more files in the crate's source used as the "source of truth" for
# the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
{ path = "LICENSE", hash = 0xbd0eed23 }
]
[[licenses.clarify]]
name = "webpki"
expression = "ISC"
license-files = [{ path = "LICENSE", hash = 0x001c7e6c }]
[licenses.private]
# If true, ignores workspace crates that aren't published, or are only
# published to private registries
ignore = false
# One or more private registries that you might publish crates to, if a crate
# is only published to private registries, and ignore is true, the crate will
# not have its license(s) checked
registries = [
#"https://sekretz.com/registry
]
# This section is considered when running `cargo deny check bans`.
# More documentation about the 'bans' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans]
# Lint level for when multiple versions of the same crate are detected
multiple-versions = "warn"
# The graph highlighting used when creating dotgraphs for crates
# with multiple versions
# * lowest-version - The path to the lowest versioned duplicate is highlighted
# * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used
highlight = "lowest-version"
# List of crates that are allowed. Use with care!
allow = [
#{ name = "ansi_term", version = "=0.11.0" },
]
# List of crates to deny
deny = [
{ name = "parity-util-mem", version = "<0.6" }
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
]
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = [
#{ name = "ansi_term", version = "=0.11.0" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite
skip-tree = [
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
]
# This section is considered when running `cargo deny check sources`.
# More documentation about the 'sources' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
[sources]
# Lint level for what to happen when a crate from a crate registry that is not
# in the allow list is encountered
unknown-registry = "deny"
# Lint level for what to happen when a crate from a git repository that is not
# in the allow list is encountered
unknown-git = "warn"
# List of URLs for allowed crate registries. Defaults to the crates.io index
# if not specified. If it is specified but empty, no registries are allowed.
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
# List of URLs for allowed Git repositories
allow-git = []
@@ -0,0 +1,30 @@
FROM docker.io/library/ubuntu:20.04
# metadata
ARG VCS_REF
ARG BUILD_DATE
LABEL io.parity.image.authors="devops-team@parity.io" \
io.parity.image.vendor="Parity Technologies" \
io.parity.image.title="parity/subkey" \
io.parity.image.description="Subkey: key generating utility for Substrate." \
io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/scripts/ci/docker/subkey.Dockerfile" \
io.parity.image.revision="${VCS_REF}" \
io.parity.image.created="${BUILD_DATE}" \
io.parity.image.documentation="https://github.com/paritytech/substrate/tree/${VCS_REF}/subkey"
# show backtraces
ENV RUST_BACKTRACE 1
# add user
RUN useradd -m -u 1000 -U -s /bin/sh -d /subkey subkey
# add subkey binary to docker image
COPY ./subkey /usr/local/bin
USER subkey
# check if executable works in this container
RUN /usr/local/bin/subkey --version
ENTRYPOINT ["/usr/local/bin/subkey"]
@@ -0,0 +1,44 @@
FROM docker.io/library/ubuntu:20.04
# metadata
ARG VCS_REF
ARG BUILD_DATE
LABEL io.parity.image.authors="devops-team@parity.io" \
io.parity.image.vendor="Parity Technologies" \
io.parity.image.title="parity/substrate" \
io.parity.image.description="Substrate: The platform for blockchain innovators." \
io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/scripts/ci/docker/Dockerfile" \
io.parity.image.revision="${VCS_REF}" \
io.parity.image.created="${BUILD_DATE}" \
io.parity.image.documentation="https://wiki.parity.io/Parity-Substrate"
# show backtraces
ENV RUST_BACKTRACE 1
# install tools and dependencies
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
libssl1.1 \
ca-certificates \
curl && \
# apt cleanup
apt-get autoremove -y && \
apt-get clean && \
find /var/lib/apt/lists/ -type f -not -name lock -delete; \
# add user
useradd -m -u 1000 -U -s /bin/sh -d /substrate substrate
# add substrate binary to docker image
COPY ./substrate /usr/local/bin
USER substrate
# check if executable works in this container
RUN /usr/local/bin/substrate --version
EXPOSE 30333 9933 9944
VOLUME ["/substrate"]
ENTRYPOINT ["/usr/local/bin/substrate"]
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -e
#shellcheck source=../common/lib.sh
source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/../common/lib.sh"
repo="$GITHUB_REPOSITORY"
pr="$GITHUB_PR"
ensure_labels() {
for label in "$@"; do
if has_label "$repo" "$pr" "$label"; then
return 0
fi
done
return 1
}
# Must have one of the following labels
releasenotes_labels=(
'B0-silent'
'B3-apinoteworthy'
'B5-clientnoteworthy'
'B7-runtimenoteworthy'
)
criticality_labels=(
'C1-low 📌'
'C3-medium 📣'
'C7-high ❗️'
'C9-critical ‼️'
)
audit_labels=(
'D1-audited 👍'
'D2-notlive 💤'
'D3-trivial 🧸'
'D5-nicetohaveaudit ⚠️'
'D9-needsaudit 👮'
)
echo "[+] Checking release notes (B) labels"
if ensure_labels "${releasenotes_labels[@]}"; then
echo "[+] Release notes label detected. All is well."
else
echo "[!] Release notes label not detected. Please add one of: ${releasenotes_labels[*]}"
exit 1
fi
echo "[+] Checking release criticality (C) labels"
if ensure_labels "${criticality_labels[@]}"; then
echo "[+] Release criticality label detected. All is well."
else
echo "[!] Release criticality label not detected. Please add one of: ${criticality_labels[*]}"
exit 1
fi
if has_runtime_changes origin/master "${HEAD_SHA}"; then
echo "[+] Runtime changes detected. Checking audit (D) labels"
if ensure_labels "${audit_labels[@]}"; then
echo "[+] Release audit label detected. All is well."
else
echo "[!] Release audit label not detected. Please add one of: ${audit_labels[*]}"
exit 1
fi
fi
exit 0
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# shellcheck source=../common/lib.sh
source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/../common/lib.sh"
version="$2"
last_version="$1"
all_changes="$(sanitised_git_logs "$last_version" "$version")"
runtime_changes=""
api_changes=""
client_changes=""
changes=""
migrations=""
while IFS= read -r line; do
pr_id=$(echo "$line" | sed -E 's/.*#([0-9]+)\)$/\1/')
# Skip if the PR has the silent label - this allows us to skip a few requests
if has_label 'paritytech/substrate' "$pr_id" 'B0-silent'; then
continue
fi
if has_label 'paritytech/substrate' "$pr_id" 'B3-apinoteworthy' ; then
api_changes="$api_changes
$line"
fi
if has_label 'paritytech/substrate' "$pr_id" 'B5-clientnoteworthy'; then
client_changes="$client_changes
$line"
fi
if has_label 'paritytech/substrate' "$pr_id" 'B7-runtimenoteworthy'; then
runtime_changes="$runtime_changes
$line"
fi
if has_label 'paritytech/substrate' "$pr_id" 'E1-runtime-migration'; then
migrations="$migrations
$line"
fi
done <<< "$all_changes"
# Make the substrate section if there are any substrate changes
if [ -n "$runtime_changes" ] ||
[ -n "$api_changes" ] ||
[ -n "$client_changes" ] ||
[ -n "$migrations" ]; then
changes=$(cat << EOF
Substrate changes
-----------------
EOF
)
if [ -n "$runtime_changes" ]; then
changes="$changes
Runtime
-------
$runtime_changes"
fi
if [ -n "$client_changes" ]; then
changes="$changes
Client
------
$client_changes"
fi
if [ -n "$api_changes" ]; then
changes="$changes
API
---
$api_changes"
fi
release_text="$release_text
$changes"
fi
if [ -n "$migrations" ]; then
changes="$changes
Runtime Migrations
------------------
$migrations"
fi
echo "$changes"
+121
View File
@@ -0,0 +1,121 @@
#!/bin/sh
#
#
# check for any changes in the node/src/runtime, frame/ and primitives/sr_* trees. if
# there are any changes found, it should mark the PR breaksconsensus and
# "auto-fail" the PR if there isn't a change in the runtime/src/lib.rs file
# that alters the version.
set -e # fail on any error
#shellcheck source=../common/lib.sh
. "$(dirname "${0}")/../common/lib.sh"
VERSIONS_FILE="bin/node/runtime/src/lib.rs"
boldprint () { printf "|\n| \033[1m%s\033[0m\n|\n" "${@}"; }
boldcat () { printf "|\n"; while read -r l; do printf "| \033[1m%s\033[0m\n" "${l}"; done; printf "|\n" ; }
github_label () {
echo
echo "# run github-api job for labeling it ${1}"
curl -sS -X POST \
-F "token=${CI_JOB_TOKEN}" \
-F "ref=master" \
-F "variables[LABEL]=${1}" \
-F "variables[PRNO]=${CI_COMMIT_REF_NAME}" \
"${GITLAB_API}/projects/${GITHUB_API_PROJECT}/trigger/pipeline"
}
boldprint "latest 10 commits of ${CI_COMMIT_REF_NAME}"
git log --graph --oneline --decorate=short -n 10
boldprint "make sure the master branch and release tag are available in shallow clones"
git fetch --depth="${GIT_DEPTH:-100}" origin master
git fetch --depth="${GIT_DEPTH:-100}" origin release
git tag -f release FETCH_HEAD
git log -n1 release
boldprint "check if the wasm sources changed"
if ! has_runtime_changes origin/master "${CI_COMMIT_SHA}"
then
boldcat <<-EOT
no changes to the runtime source code detected
EOT
exit 0
fi
# check for spec_version updates: if the spec versions changed, then there is
# consensus-critical logic that has changed. the runtime wasm blobs must be
# rebuilt.
add_spec_version="$(git diff tags/release ${CI_COMMIT_SHA} -- "${VERSIONS_FILE}" \
| sed -n -r "s/^\+[[:space:]]+spec_version: +([0-9]+),$/\1/p")"
sub_spec_version="$(git diff tags/release ${CI_COMMIT_SHA} -- "${VERSIONS_FILE}" \
| sed -n -r "s/^\-[[:space:]]+spec_version: +([0-9]+),$/\1/p")"
if [ "${add_spec_version}" != "${sub_spec_version}" ]
then
boldcat <<-EOT
changes to the runtime sources and changes in the spec version.
spec_version: ${sub_spec_version} -> ${add_spec_version}
EOT
exit 0
else
# check for impl_version updates: if only the impl versions changed, we assume
# there is no consensus-critical logic that has changed.
add_impl_version="$(git diff tags/release ${CI_COMMIT_SHA} -- "${VERSIONS_FILE}" \
| sed -n -r 's/^\+[[:space:]]+impl_version: +([0-9]+),$/\1/p')"
sub_impl_version="$(git diff tags/release ${CI_COMMIT_SHA} -- "${VERSIONS_FILE}" \
| sed -n -r 's/^\-[[:space:]]+impl_version: +([0-9]+),$/\1/p')"
# see if the impl version changed
if [ "${add_impl_version}" != "${sub_impl_version}" ]
then
boldcat <<-EOT
changes to the runtime sources and changes in the impl version.
impl_version: ${sub_impl_version} -> ${add_impl_version}
EOT
exit 0
fi
boldcat <<-EOT
wasm source files changed but not the spec/impl version. If changes made do not alter logic,
just bump 'impl_version'. If they do change logic, bump 'spec_version'.
source file directories:
- bin/node/src/runtime
- frame
- primitives/sr-*
versions file: ${VERSIONS_FILE}
EOT
fi
# dropped through. there's something wrong; exit 1.
exit 1
# vim: noexpandtab
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# shellcheck source=../common/lib.sh
source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/../common/lib.sh"
version="$CI_COMMIT_TAG"
echo '[+] Checking tag has been signed'
check_tag "paritytech/substrate" "$version"
case $? in
0) echo '[+] Tag found and has been signed'; exit 0
;;
1) echo '[!] Tag found but has not been signed. Aborting release.'; exit 1
;;
2) echo '[!] Tag not found. Aborting release.'; exit 1
esac
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# The script is meant to check if the rules regarding packages
# dependencies are satisfied.
# The general format is:
# [top-lvl-dir] MESSAGE/[other-top-dir]
# For instance no crate within `./client` directory
# is allowed to import any crate with a directory path containing `frame`.
# Such rule is just: `client crates must not depend on anything in /frame`.
# The script should be run from the main repo directory!
set -u
# HARD FAILING
MUST_NOT=(
"client crates must not depend on anything in /frame"
"client crates must not depend on anything in /node"
"frame crates must not depend on anything in /node"
"frame crates must not depend on anything in /client"
"primitives crates must not depend on anything in /frame"
)
# ONLY DISPLAYED, script still succeeds
PLEASE_DONT=(
"primitives crates should not depend on anything in /client"
)
VIOLATIONS=()
PACKAGES=()
function check_rule() {
rule=$1
from=$(echo $rule | cut -f1 -d\ )
to=$(echo $rule | cut -f2 -d\/)
cd $from
echo "Checking rule '$rule'"
packages=$(find -name Cargo.toml | xargs grep -wn "path.*\.\.\/$to")
has_references=$(echo -n $packages | wc -c)
if [ "$has_references" != "0" ]; then
VIOLATIONS+=("$rule")
# Find packages that violate:
PACKAGES+=("$packages")
fi
cd - > /dev/null
}
for rule in "${MUST_NOT[@]}"
do
check_rule "$rule";
done
# Only the MUST NOT will be counted towards failure
HARD_VIOLATIONS=${#VIOLATIONS[@]}
for rule in "${PLEASE_DONT[@]}"
do
check_rule "$rule";
done
# Display violations and fail
I=0
for v in "${VIOLATIONS[@]}"
do
cat << EOF
===========================================
======= Violation of rule: $v
===========================================
${PACKAGES[$I]}
EOF
I=$I+1
done
exit $HARD_VIOLATIONS
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# shellcheck source=../common/lib.sh
source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/../common/lib.sh"
version="$CI_COMMIT_TAG"
# Note that this is not the last *tagged* version, but the last *published* version
last_version=$(last_github_release 'paritytech/substrate')
release_text="$(./generate_release_text.sh "$last_version" "$version")"
echo "[+] Pushing release to github"
# Create release on github
release_name="Substrate $version"
data=$(jq -Rs --arg version "$version" \
--arg release_name "$release_name" \
--arg release_text "$release_text" \
'{
"tag_name": $version,
"target_commitish": "master",
"name": $release_name,
"body": $release_text,
"draft": true,
"prerelease": false
}' < /dev/null)
out=$(curl -s -X POST --data "$data" -H "Authorization: token $GITHUB_RELEASE_TOKEN" "$api_base/paritytech/substrate/releases")
html_url=$(echo "$out" | jq -r .html_url)
if [ "$html_url" == "null" ]
then
echo "[!] Something went wrong posting:"
echo "$out"
else
echo "[+] Release draft created: $html_url"
fi
echo '[+] Sending draft release URL to Matrix'
msg_body=$(cat <<EOF
**Release pipeline for Substrate $version complete.**
Draft release created: $html_url
EOF
)
formatted_msg_body=$(cat <<EOF
<strong>Release pipeline for Substrate $version complete.</strong><br />
Draft release created: $html_url
EOF
)
send_message "$(structure_message "$msg_body" "$formatted_msg_body")" "$MATRIX_ROOM_ID" "$MATRIX_ACCESS_TOKEN"
echo "[+] Done! Maybe the release worked..."
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
url="https://api.github.com/repos/paritytech/substrate/pulls/${CI_COMMIT_REF_NAME}"
echo "[+] API URL: $url"
draft_state=$(curl -H "Authorization: token ${GITHUB_PR_TOKEN}" "$url" | jq -r .draft)
echo "[+] Draft state: $draft_state"
if [ "$draft_state" = 'true' ]; then
echo "[!] PR is currently a draft, stopping pipeline"
exit 1
else
echo "[+] PR is not a draft. Proceeding with CI pipeline"
exit 0
fi
@@ -0,0 +1,239 @@
rule_files:
- /dev/stdin
evaluation_interval: 1m
tests:
- interval: 1m
input_series:
- series: 'substrate_sub_libp2p_peers_count{
job="substrate",
pod="substrate-abcdef01234-abcdef",
instance="substrate-abcdef01234-abcdef",
}'
values: '3 2+0x4 1+0x9' # 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
- series: 'substrate_sub_txpool_validations_scheduled{
job="substrate",
pod="substrate-abcdef01234-abcdef",
instance="substrate-abcdef01234-abcdef",
}'
values: '11+1x10 22+2x30 10043x5'
- series: 'substrate_sub_txpool_validations_finished{
job="substrate",
pod="substrate-abcdef01234-abcdef",
instance="substrate-abcdef01234-abcdef",
}'
values: '0+1x42 42x5'
- series: 'substrate_block_height{
status="best", job="substrate",
pod="substrate-abcdef01234-abcdef",
instance="substrate-abcdef01234-abcdef",
}'
values: '1+1x3 4+0x13' # 1 2 3 4 4 4 4 4 4 4 4 4 ...
- series: 'substrate_block_height{
status="finalized",
job="substrate",
pod="substrate-abcdef01234-abcdef",
instance="substrate-abcdef01234-abcdef",
}'
values: '1+1x3 4+0x13' # 1 2 3 4 4 4 4 4 4 4 4 4 ...
alert_rule_test:
######################################################################
# Block production
######################################################################
- eval_time: 6m
alertname: BlockProductionSlow
exp_alerts:
- eval_time: 7m
alertname: BlockProductionSlow
exp_alerts:
- exp_labels:
severity: warning
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
status: best
exp_annotations:
message: "Best block on instance
substrate-abcdef01234-abcdef increases by less than 1 per
minute for more than 3 minutes."
- eval_time: 14m
alertname: BlockProductionSlow
exp_alerts:
- exp_labels:
severity: warning
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
status: best
exp_annotations:
message: "Best block on instance
substrate-abcdef01234-abcdef increases by less than 1 per
minute for more than 3 minutes."
- exp_labels:
severity: critical
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
status: best
exp_annotations:
message: "Best block on instance
substrate-abcdef01234-abcdef increases by less than 1 per
minute for more than 10 minutes."
######################################################################
# Block finalization
######################################################################
- eval_time: 6m
alertname: BlockFinalizationSlow
exp_alerts:
- eval_time: 7m
alertname: BlockFinalizationSlow
exp_alerts:
- exp_labels:
severity: warning
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
status: finalized
exp_annotations:
message: "Finalized block on instance
substrate-abcdef01234-abcdef increases by less than 1 per
minute for more than 3 minutes."
- eval_time: 14m
alertname: BlockFinalizationSlow
exp_alerts:
- exp_labels:
severity: warning
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
status: finalized
exp_annotations:
message: "Finalized block on instance
substrate-abcdef01234-abcdef increases by less than 1 per
minute for more than 3 minutes."
- exp_labels:
severity: critical
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
status: finalized
exp_annotations:
message: "Finalized block on instance
substrate-abcdef01234-abcdef increases by less than 1 per
minute for more than 10 minutes."
######################################################################
# Transaction queue
######################################################################
- eval_time: 11m
alertname: TransactionQueueSizeIncreasing
# Number of validations scheduled and finished both grow at a rate
# of 1 in the first 10 minutes, thereby the queue is not increasing
# in size, thus don't expect an alert.
exp_alerts:
- eval_time: 22m
alertname: TransactionQueueSizeIncreasing
# Number of validations scheduled is growing twice as fast as the
# number of validations finished after minute 10. Thus expect
# warning alert after 20 minutes.
exp_alerts:
- exp_labels:
severity: warning
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
exp_annotations:
message: "The transaction pool size on node
substrate-abcdef01234-abcdef has been monotonically
increasing for more than 10 minutes."
- eval_time: 43m
alertname: TransactionQueueSizeIncreasing
# Number of validations scheduled is growing twice as fast as the
# number of validations finished after minute 10. Thus expect
# both warning and critical alert after 40 minutes.
exp_alerts:
- exp_labels:
severity: warning
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
exp_annotations:
message: "The transaction pool size on node
substrate-abcdef01234-abcdef has been monotonically
increasing for more than 10 minutes."
- exp_labels:
severity: warning
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
exp_annotations:
message: "The transaction pool size on node
substrate-abcdef01234-abcdef has been monotonically
increasing for more than 30 minutes."
- eval_time: 49m
alertname: TransactionQueueSizeHigh
# After minute 43 the number of validations scheduled jumps up
# drastically while the number of validations finished stays the
# same. Thus expect an alert.
exp_alerts:
- exp_labels:
severity: warning
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
exp_annotations:
message: "The transaction pool size on node
substrate-abcdef01234-abcdef has been above 10_000 for more
than 5 minutes."
######################################################################
# Networking
######################################################################
- eval_time: 3m # Values: 3 2 2
alertname: NumberOfPeersLow
exp_alerts:
- eval_time: 4m # Values: 2 2 2
alertname: NumberOfPeersLow
exp_alerts:
- exp_labels:
severity: warning
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
exp_annotations:
message: "The node substrate-abcdef01234-abcdef has less
than 3 peers for more than 3 minutes"
- eval_time: 16m # Values: 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1
alertname: NumberOfPeersLow
exp_alerts:
- exp_labels:
severity: warning
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
exp_annotations:
message: "The node substrate-abcdef01234-abcdef has less
than 3 peers for more than 3 minutes"
- exp_labels:
severity: critical
pod: substrate-abcdef01234-abcdef
instance: substrate-abcdef01234-abcdef
job: substrate
exp_annotations:
message: "The node substrate-abcdef01234-abcdef has less
than 3 peers for more than 15 minutes"
@@ -0,0 +1,171 @@
groups:
- name: substrate.rules
rules:
##############################################################################
# Block production
##############################################################################
- alert: BlockProductionSlow
annotations:
message: 'Best block on instance {{ $labels.instance }} increases by
less than 1 per minute for more than 3 minutes.'
expr: increase(substrate_block_height{status="best"}[1m]) < 1
for: 3m
labels:
severity: warning
- alert: BlockProductionSlow
annotations:
message: 'Best block on instance {{ $labels.instance }} increases by
less than 1 per minute for more than 10 minutes.'
expr: increase(substrate_block_height{status="best"}[1m]) < 1
for: 10m
labels:
severity: critical
##############################################################################
# Block finalization
##############################################################################
- alert: BlockFinalizationSlow
expr: increase(substrate_block_height{status="finalized"}[1m]) < 1
for: 3m
labels:
severity: warning
annotations:
message: 'Finalized block on instance {{ $labels.instance }} increases by
less than 1 per minute for more than 3 minutes.'
- alert: BlockFinalizationSlow
expr: increase(substrate_block_height{status="finalized"}[1m]) < 1
for: 10m
labels:
severity: critical
annotations:
message: 'Finalized block on instance {{ $labels.instance }} increases by
less than 1 per minute for more than 10 minutes.'
- alert: BlockFinalizationLaggingBehind
# Under the assumption of an average block production of 6 seconds,
# "best" and "finalized" being more than 10 blocks apart would imply
# more than a 1 minute delay between block production and finalization.
expr: '(substrate_block_height{status="best"} - ignoring(status)
substrate_block_height{status="finalized"}) > 10'
for: 8m
labels:
severity: critical
annotations:
message: "Block finalization on instance {{ $labels.instance }} is behind
block production by {{ $value }} for more than 8 minutes."
##############################################################################
# Transaction queue
##############################################################################
- alert: TransactionQueueSizeIncreasing
expr: 'increase(substrate_sub_txpool_validations_scheduled[5m]) -
increase(substrate_sub_txpool_validations_finished[5m]) > 0'
for: 10m
labels:
severity: warning
annotations:
message: 'The transaction pool size on node {{ $labels.instance }} has
been monotonically increasing for more than 10 minutes.'
- alert: TransactionQueueSizeIncreasing
expr: 'increase(substrate_sub_txpool_validations_scheduled[5m]) -
increase(substrate_sub_txpool_validations_finished[5m]) > 0'
for: 30m
labels:
severity: warning
annotations:
message: 'The transaction pool size on node {{ $labels.instance }} has
been monotonically increasing for more than 30 minutes.'
- alert: TransactionQueueSizeHigh
expr: 'substrate_sub_txpool_validations_scheduled -
substrate_sub_txpool_validations_finished > 10000'
for: 5m
labels:
severity: warning
annotations:
message: 'The transaction pool size on node {{ $labels.instance }} has
been above 10_000 for more than 5 minutes.'
##############################################################################
# Networking
##############################################################################
- alert: NumberOfPeersLow
expr: substrate_sub_libp2p_peers_count < 3
for: 3m
labels:
severity: warning
annotations:
message: 'The node {{ $labels.instance }} has less than 3 peers for more
than 3 minutes'
- alert: NumberOfPeersLow
expr: substrate_sub_libp2p_peers_count < 3
for: 15m
labels:
severity: critical
annotations:
message: 'The node {{ $labels.instance }} has less than 3 peers for more
than 15 minutes'
- alert: NoIncomingConnection
expr: increase(substrate_sub_libp2p_incoming_connections_total[20m]) == 0
labels:
severity: warning
annotations:
message: 'The node {{ $labels.instance }} has not received any new incoming
TCP connection in the past 20 minutes. Is it connected to the Internet?'
##############################################################################
# System
##############################################################################
- alert: NumberOfFileDescriptorsHigh
expr: 'node_filefd_allocated{chain!=""} > 10000'
for: 3m
labels:
severity: warning
annotations:
message: 'The node {{ $labels.instance }} has more than 10_000 file
descriptors allocated for more than 3 minutes'
##############################################################################
# Others
##############################################################################
- alert: AuthorityDiscoveryDiscoveryFailureHigh
expr: 'substrate_authority_discovery_handle_value_found_event_failure /
ignoring(name)
substrate_authority_discovery_dht_event_received{name="value_found"} > 0.5'
for: 2h
labels:
severity: warning
annotations:
message: 'Authority discovery on node {{ $labels.instance }} fails to
process more than 50 % of the values found on the DHT for more than 2
hours.'
- alert: UnboundedChannelPersistentlyLarge
expr: '(
(substrate_unbounded_channel_len{action = "send"} -
ignoring(action) substrate_unbounded_channel_len{action = "received"})
or on(instance) substrate_unbounded_channel_len{action = "send"}
) >= 200'
for: 5m
labels:
severity: warning
annotations:
message: 'Channel {{ $labels.entity }} on node {{ $labels.instance }} contains
more than 200 items for more than 5 minutes. Node might be frozen.'
- alert: UnboundedChannelVeryLarge
expr: '(
(substrate_unbounded_channel_len{action = "send"} -
ignoring(action) substrate_unbounded_channel_len{action = "received"})
or on(instance) substrate_unbounded_channel_len{action = "send"}
) > 15000'
labels:
severity: warning
annotations:
message: 'Channel {{ $labels.entity }} on node {{ $labels.instance }} contains more than
15000 items.'
@@ -0,0 +1,7 @@
## Substrate Dashboard
Shared templated Grafana dashboards.
To import the dashboards follow the [Grafana
documentation](https://grafana.com/docs/grafana/latest/reference/export_import/).
You can see an example setup [here](./substrate-networking.json).
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -e
export TERM=xterm
PROJECT_ROOT=`git rev-parse --show-toplevel`
if [ "$#" -ne 1 ]; then
echo "node-template-release.sh path_to_target_archive"
exit 1
fi
PATH_TO_ARCHIVE=$1
cd $PROJECT_ROOT/scripts/ci/node-template-release
cargo run $PROJECT_ROOT/bin/node-template $PROJECT_ROOT/$PATH_TO_ARCHIVE
@@ -0,0 +1,21 @@
[package]
name = "node-template-release"
version = "3.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "GPL-3.0"
[dependencies]
toml = "0.4"
tar = "0.4"
glob = "0.2"
clap = { version = "3.0", features = ["derive"] }
tempfile = "3"
fs_extra = "1"
git2 = "0.8"
flate2 = "1.0"
[workspace]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
@@ -0,0 +1,279 @@
use clap::Parser;
use std::{
collections::HashMap,
fs::{self, File, OpenOptions},
io::{Read, Write},
path::{Path, PathBuf},
process::Command,
};
use glob;
use fs_extra::dir::{self, CopyOptions};
use tempfile;
use git2;
use toml;
use tar;
use flate2::{write::GzEncoder, Compression};
const SUBSTRATE_GIT_URL: &str = "https://github.com/paritytech/substrate.git";
type CargoToml = HashMap<String, toml::Value>;
#[derive(Parser)]
struct Options {
/// The path to the `node-template` source.
#[clap(parse(from_os_str))]
node_template: PathBuf,
/// The path where to output the generated `tar.gz` file.
#[clap(parse(from_os_str))]
output: PathBuf,
}
/// Find all `Cargo.toml` files in the given path.
fn find_cargo_tomls(path: PathBuf) -> Vec<PathBuf> {
let path = format!("{}/**/*.toml", path.display());
let glob = glob::glob(&path).expect("Generates globbing pattern");
let mut result = Vec::new();
glob.into_iter().for_each(|file| match file {
Ok(file) => result.push(file),
Err(e) => println!("{:?}", e),
});
if result.is_empty() {
panic!("Did not found any `Cargo.toml` files.");
}
result
}
/// Copy the `node-template` to the given path.
fn copy_node_template(node_template: &Path, dest_path: &Path) {
let options = CopyOptions::new();
dir::copy(node_template, dest_path, &options).expect("Copies node-template to tmp dir");
}
/// Gets the latest commit id of the repository given by `path`.
fn get_git_commit_id(path: &Path) -> String {
let repo = git2::Repository::discover(path)
.expect(&format!("Node template ({}) should be in a git repository.", path.display()));
let commit_id = repo
.head()
.expect("Repository should have a head")
.peel_to_commit()
.expect("Head references a commit")
.id();
format!("{}", commit_id)
}
/// Parse the given `Cargo.toml` into a `HashMap`
fn parse_cargo_toml(file: &Path) -> CargoToml {
let mut content = String::new();
File::open(file)
.expect("Cargo.toml exists")
.read_to_string(&mut content)
.expect("Reads file");
toml::from_str(&content).expect("Cargo.toml is a valid toml file")
}
/// Replaces all substrate path dependencies with a git dependency.
fn replace_path_dependencies_with_git(
cargo_toml_path: &Path,
commit_id: &str,
cargo_toml: &mut CargoToml,
) {
let mut cargo_toml_path = cargo_toml_path.to_path_buf();
// remove `Cargo.toml`
cargo_toml_path.pop();
for &table in &["dependencies", "build-dependencies", "dev-dependencies"] {
let mut dependencies: toml::value::Table =
match cargo_toml.remove(table).and_then(|v| v.try_into().ok()) {
Some(deps) => deps,
None => continue,
};
let deps_rewritten = dependencies
.iter()
.filter_map(|(k, v)| {
v.clone().try_into::<toml::value::Table>().ok().map(move |v| (k, v))
})
.filter(|t| {
t.1.contains_key("path") && {
// if the path does not exists, we need to add this as git dependency
t.1.get("path")
.unwrap()
.as_str()
.map(|path| !cargo_toml_path.join(path).exists())
.unwrap_or(false)
}
})
.map(|(k, mut v)| {
// remove `path` and add `git` and `rev`
v.remove("path");
v.insert("git".into(), SUBSTRATE_GIT_URL.into());
v.insert("rev".into(), commit_id.into());
(k.clone(), v.into())
})
.collect::<HashMap<_, _>>();
dependencies.extend(deps_rewritten.into_iter());
cargo_toml.insert(table.into(), dependencies.into());
}
}
/// Update the top level (workspace) `Cargo.toml` file.
///
/// - Adds `profile.release` = `panic = unwind`
/// - Adds `workspace` definition
fn update_top_level_cargo_toml(
cargo_toml: &mut CargoToml,
workspace_members: Vec<&PathBuf>,
node_template_path: &Path,
) {
let mut panic_unwind = toml::value::Table::new();
panic_unwind.insert("panic".into(), "unwind".into());
let mut profile = toml::value::Table::new();
profile.insert("release".into(), panic_unwind.into());
cargo_toml.insert("profile".into(), profile.into());
let members = workspace_members
.iter()
.map(|p| {
p.strip_prefix(node_template_path)
.expect("Workspace member is a child of the node template path!")
.parent()
// We get the `Cargo.toml` paths as workspace members, but for the `members` field
// we just need the path.
.expect("The given path ends with `Cargo.toml` as file name!")
.display()
.to_string()
})
.collect::<Vec<_>>();
let mut members_section = toml::value::Table::new();
members_section.insert("members".into(), members.into());
cargo_toml.insert("workspace".into(), members_section.into());
}
fn write_cargo_toml(path: &Path, cargo_toml: CargoToml) {
let content = toml::to_string_pretty(&cargo_toml).expect("Creates `Cargo.toml`");
let mut file = File::create(path).expect(&format!("Creates `{}`.", path.display()));
write!(file, "{}", content).expect("Writes `Cargo.toml`");
}
/// Build and test the generated node-template
fn build_and_test(path: &Path, cargo_tomls: &[PathBuf]) {
// Build node
assert!(Command::new("cargo")
.args(&["build", "--all"])
.current_dir(path)
.status()
.expect("Compiles node")
.success());
// Test node
assert!(Command::new("cargo")
.args(&["test", "--all"])
.current_dir(path)
.status()
.expect("Tests node")
.success());
// Remove all `target` directories
for toml in cargo_tomls {
let mut target_path = toml.clone();
target_path.pop();
target_path = target_path.join("target");
if target_path.exists() {
fs::remove_dir_all(&target_path)
.expect(&format!("Removes `{}`", target_path.display()));
}
}
}
fn main() {
let options = Options::parse();
let build_dir = tempfile::tempdir().expect("Creates temp build dir");
let node_template_folder = options
.node_template
.canonicalize()
.expect("Node template path exists")
.file_name()
.expect("Node template folder is last element of path")
.to_owned();
// The path to the node-template in the build dir.
let node_template_path = build_dir.path().join(node_template_folder);
copy_node_template(&options.node_template, build_dir.path());
let mut cargo_tomls = find_cargo_tomls(build_dir.path().to_owned());
let commit_id = get_git_commit_id(&options.node_template);
let top_level_cargo_toml_path = node_template_path.join("Cargo.toml");
// Check if top level Cargo.toml exists. If not, create one in the destination
if !cargo_tomls.contains(&top_level_cargo_toml_path) {
// create the top_level_cargo_toml
OpenOptions::new()
.create(true)
.write(true)
.open(top_level_cargo_toml_path.clone())
.expect("Create root level `Cargo.toml` failed.");
// push into our data structure
cargo_tomls.push(PathBuf::from(top_level_cargo_toml_path.clone()));
}
cargo_tomls.iter().for_each(|t| {
let mut cargo_toml = parse_cargo_toml(&t);
replace_path_dependencies_with_git(&t, &commit_id, &mut cargo_toml);
// Check if this is the top level `Cargo.toml`, as this requires some special treatments.
if top_level_cargo_toml_path == *t {
// All workspace member `Cargo.toml` file paths.
let workspace_members =
cargo_tomls.iter().filter(|p| **p != top_level_cargo_toml_path).collect();
update_top_level_cargo_toml(&mut cargo_toml, workspace_members, &node_template_path);
}
write_cargo_toml(&t, cargo_toml);
});
// adding root rustfmt to node template build path
let node_template_rustfmt_toml_path = node_template_path.join("rustfmt.toml");
let root_rustfmt_toml = &options.node_template.join("../../rustfmt.toml");
if root_rustfmt_toml.exists() {
fs::copy(&root_rustfmt_toml, &node_template_rustfmt_toml_path)
.expect("Copying rustfmt.toml.");
}
build_and_test(&node_template_path, &cargo_tomls);
let output = GzEncoder::new(
File::create(&options.output).expect("Creates output file"),
Compression::default(),
);
let mut tar = tar::Builder::new(output);
tar.append_dir_all("substrate-node-template", node_template_path)
.expect("Writes substrate-node-template archive");
}