cassandra/.jenkins/k8s/jenkins-test.sh

118 lines
5.0 KiB
Bash
Executable File

#!/bin/bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
#
# Validates the CI declarations under .jenkins/ without deploying anything:
# - the Jenkinsfile parses as groovy
# - jenkins-deployment.yaml renders through the jenkins helm chart
# - the yaml embedded in it (agent pod templates, JCasC config scripts) parses
#
# Requires: helm, python3 with pyyaml, and either groovy or docker.
# Run from anywhere: .jenkins/k8s/jenkins-test.sh
set -e
CASSANDRA_DIR="$(cd "$(dirname "$0")/../.." > /dev/null && pwd)"
JENKINS_DIR="${CASSANDRA_DIR}/.jenkins"
status=0
command -v helm > /dev/null || { echo "helm must be installed and in the PATH"; exit 1; }
command -v python3 > /dev/null || { echo "python3 must be installed and in the PATH"; exit 1; }
python3 -c "import yaml" 2> /dev/null || { echo "python3 pyyaml must be installed: pip install pyyaml"; exit 1; }
echo "== Jenkinsfile groovy syntax"
# Phases.CONVERSION parses and builds the AST without resolving the pipeline DSL or @NonCPS,
# neither of which exist outside a jenkins controller
syntax_check_dir="$(mktemp -d)"
syntax_check="${syntax_check_dir}/syntax-check.groovy"
# mktemp gives 0700, which the unprivileged user inside the groovy image cannot traverse
chmod 755 "${syntax_check_dir}"
cat > "${syntax_check}" << 'EOF'
import org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.Phases
def cu = new CompilationUnit()
args.each { cu.addSource(new File(it)) }
cu.compile(Phases.CONVERSION)
println " ${args.join(', ')} parses"
EOF
if command -v groovy > /dev/null ; then
groovy "${syntax_check}" "${JENKINS_DIR}/Jenkinsfile" || status=1
elif command -v docker > /dev/null ; then
# absolute paths, the image's working directory is not where the script was mounted
docker run --rm -v "${syntax_check_dir}:/check:ro" -v "${JENKINS_DIR}:/jenkins:ro" \
groovy:4.0-jdk17 groovy /check/syntax-check.groovy /jenkins/Jenkinsfile || status=1
else
echo " SKIPPED: neither groovy nor docker found"
fi
echo "== jenkins-deployment.yaml renders through the helm chart"
helm repo add jenkins https://charts.jenkins.io > /dev/null
helm repo update > /dev/null
# --namespace and a release name only so the chart's templates have something to interpolate
helm template cassius jenkins/jenkins --namespace default -f "${JENKINS_DIR}/k8s/jenkins-deployment.yaml" > /dev/null \
&& echo " jenkins-deployment.yaml renders" || status=1
echo "== yaml embedded in jenkins-deployment.yaml"
python3 - "${JENKINS_DIR}/k8s" << 'EOF' || status=1
import sys, yaml
from pathlib import Path
k8s_dir = Path(sys.argv[1])
errors = 0
for path in sorted(k8s_dir.glob("*.yaml")):
try:
values = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
except yaml.YAMLError as error:
print(f" INVALID {path.name}: {error}")
errors += 1
continue
print(f" {path.name} parses")
# the values in these two maps are themselves yaml documents, and a chart never validates them —
# a misindented pod template reaches the kubernetes plugin and silently loses its agents
for keys in (("agent", "podTemplates"), ("controller", "JCasC", "configScripts")):
embedded = values
for key in keys:
embedded = embedded.get(key, {}) if isinstance(embedded, dict) else {}
for name, document in (embedded or {}).items():
location = f"{path.name} {'.'.join(keys)}.{name}"
try:
parsed = yaml.safe_load(document)
except yaml.YAMLError as error:
print(f" INVALID {location}: {error}")
errors += 1
continue
print(f" {location} parses")
# each pod template may carry a raw kubernetes pod spec in a nested `yaml` key
for template in parsed if isinstance(parsed, list) else []:
if isinstance(template, dict) and "yaml" in template:
try:
yaml.safe_load(template["yaml"])
except yaml.YAMLError as error:
print(f" INVALID {location}.yaml: {error}")
errors += 1
else:
print(f" {location}.yaml parses")
sys.exit(1 if errors else 0)
EOF
[ 0 -eq ${status} ] && echo "== all .jenkins/ checks passed" || echo "== FAILED"
exit ${status}