kind_provisioner.sh 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. #!/bin/bash
  2. # Licensed to the LF AI & Data foundation under one
  3. # or more contributor license agreements. See the NOTICE file
  4. # distributed with this work for additional information
  5. # regarding copyright ownership. The ASF licenses this file
  6. # to you under the Apache License, Version 2.0 (the
  7. # "License"); you may not use this file except in compliance
  8. # with the License. You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. # WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY
  18. #
  19. # The original version of this file is located in the https://github.com/istio/common-files repo.
  20. # If you're looking at this file in a different repo and want to make a change, please go to the
  21. # common-files repo, make the change there and check it in. Then come back to this repo and run
  22. # "make update-common".
  23. # Copyright Istio Authors
  24. #
  25. # Licensed under the Apache License, Version 2.0 (the "License");
  26. # you may not use this file except in compliance with the License.
  27. # You may obtain a copy of the License at
  28. #
  29. # http://www.apache.org/licenses/LICENSE-2.0
  30. #
  31. # Unless required by applicable law or agreed to in writing, software
  32. # distributed under the License is distributed on an "AS IS" BASIS,
  33. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  34. # See the License for the specific language governing permissions and
  35. # limitations under the License.
  36. # Exit immediately for non zero status
  37. set -e
  38. # Print commands
  39. set -x
  40. # The purpose of this file is to unify prow/lib.sh in both istio and istio.io
  41. # repos to avoid code duplication.
  42. ####################################################################
  43. ################# COMMON SECTION ###############################
  44. ####################################################################
  45. # DEFAULT_KIND_IMAGE is used to set the Kubernetes version for KinD unless overridden in params to setup_kind_cluster(s)
  46. DEFAULT_KIND_IMAGE="kindest/node:v1.20.2"
  47. SOURCE="${BASH_SOURCE[0]}"
  48. while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  49. DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  50. SOURCE="$(readlink "$SOURCE")"
  51. [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
  52. done
  53. ROOT="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
  54. UNAME="$(uname -s)"
  55. case "${UNAME}" in
  56. Linux*) MACHINE=Linux;;
  57. Darwin*) MACHINE=Mac;;
  58. CYGWIN*) MACHINE=Cygwin;;
  59. MINGW*) MACHINE=MinGw;;
  60. *) MACHINE="UNKNOWN:${UNAME}"
  61. esac
  62. # load_cluster_topology function reads cluster configuration topology file and
  63. # sets up environment variables used by other functions. So this should be called
  64. # before anything else.
  65. #
  66. # Note: Cluster configuration topology file specifies basic configuration of each
  67. # KinD cluster like its name, pod and service subnets and network_id. If two cluster
  68. # have the same network_id then they belong to the same network and their pods can
  69. # talk to each other directly.
  70. #
  71. # [{ "cluster_name": "cluster1","pod_subnet": "10.10.0.0/16","svc_subnet": "10.255.10.0/24","network_id": "0" },
  72. # { "cluster_name": "cluster2","pod_subnet": "10.20.0.0/16","svc_subnet": "10.255.20.0/24","network_id": "0" },
  73. # { "cluster_name": "cluster3","pod_subnet": "10.30.0.0/16","svc_subnet": "10.255.30.0/24","network_id": "1" }]
  74. function load_cluster_topology() {
  75. CLUSTER_TOPOLOGY_CONFIG_FILE="${1}"
  76. if [[ ! -f "${CLUSTER_TOPOLOGY_CONFIG_FILE}" ]]; then
  77. echo 'cluster topology configuration file is not specified'
  78. exit 1
  79. fi
  80. export CLUSTER_NAMES
  81. export CLUSTER_POD_SUBNETS
  82. export CLUSTER_SVC_SUBNETS
  83. export CLUSTER_NETWORK_ID
  84. KUBE_CLUSTERS=$(jq '.[] | select(.kind == "Kubernetes" or .kind == null)' "${CLUSTER_TOPOLOGY_CONFIG_FILE}")
  85. while read -r value; do
  86. CLUSTER_NAMES+=("$value")
  87. done < <(echo "${KUBE_CLUSTERS}" | jq -r '.cluster_name // .clusterName')
  88. while read -r value; do
  89. CLUSTER_POD_SUBNETS+=("$value")
  90. done < <(echo "${KUBE_CLUSTERS}" | jq -r '.pod_subnet // .podSubnet')
  91. while read -r value; do
  92. CLUSTER_SVC_SUBNETS+=("$value")
  93. done < <(echo "${KUBE_CLUSTERS}" | jq -r '.svc_subnet // .svcSubnet')
  94. while read -r value; do
  95. CLUSTER_NETWORK_ID+=("$value")
  96. done < <(echo "${KUBE_CLUSTERS}" | jq -r '.network_id // .network')
  97. export NUM_CLUSTERS
  98. NUM_CLUSTERS=$(echo "${KUBE_CLUSTERS}" | jq -s 'length')
  99. echo "${CLUSTER_NAMES[@]}"
  100. echo "${CLUSTER_POD_SUBNETS[@]}"
  101. echo "${CLUSTER_SVC_SUBNETS[@]}"
  102. echo "${CLUSTER_NETWORK_ID[@]}"
  103. echo "${NUM_CLUSTERS}"
  104. }
  105. #####################################################################
  106. ################### SINGLE-CLUSTER SECTION ######################
  107. #####################################################################
  108. # cleanup_kind_cluster takes a single parameter NAME
  109. # and deletes the KinD cluster with that name
  110. function cleanup_kind_cluster() {
  111. echo "Test exited with exit code $?."
  112. NAME="${1}"
  113. if [[ -z "${SKIP_EXPORT_LOGS:-}" ]]; then
  114. kind export logs --name "${NAME}" "${ARTIFACTS}/kind" -v9 || true
  115. fi
  116. if [[ -z "${SKIP_CLEANUP:-}" ]]; then
  117. echo "Cleaning up kind cluster"
  118. kind delete cluster --name "${NAME}" -v9 || true
  119. docker network rm kind > /dev/null 2>&1 || true
  120. fi
  121. }
  122. # check_default_cluster_yaml checks the presence of default cluster YAML
  123. # It returns 1 if it is not present
  124. function check_default_cluster_yaml() {
  125. if [[ -z "${DEFAULT_CLUSTER_YAML}" ]]; then
  126. echo 'DEFAULT_CLUSTER_YAML file must be specified. Exiting...'
  127. return 1
  128. fi
  129. }
  130. # setup_kind_cluster creates new KinD cluster with given name, image and configuration
  131. # 1. NAME: Name of the Kind cluster (optional)
  132. # 2. IMAGE: Node image used by KinD (optional)
  133. # 3. CONFIG: KinD cluster configuration YAML file. If not specified then DEFAULT_CLUSTER_YAML is used
  134. # 4. NOMETALBINSTALL: Dont install matllb if set true.
  135. # 5. CRON_LOGGER_INSTALL: Install Cron Logger if set true.
  136. # This function returns 0 when everything goes well, or 1 otherwise
  137. # If Kind cluster was already created then it would be cleaned up in case of errors
  138. function setup_kind_cluster() {
  139. NAME="${1:-kind}"
  140. IMAGE="${2:-"${DEFAULT_KIND_IMAGE}"}"
  141. CONFIG="${3:-}"
  142. NOMETALBINSTALL="${4:-false}"
  143. CRON_LOGGER_INSTALL="${5:-true}"
  144. check_default_cluster_yaml
  145. # Delete any previous KinD cluster
  146. echo "Deleting previous KinD cluster with name=${NAME}"
  147. if ! (kind delete cluster --name="${NAME}" -v9) > /dev/null; then
  148. echo "No existing kind cluster with name ${NAME}. Continue..."
  149. else
  150. docker network rm kind > /dev/null 2>&1 || true
  151. fi
  152. # explicitly disable shellcheck since we actually want $NAME to expand now
  153. # shellcheck disable=SC2064
  154. trap "cleanup_kind_cluster ${NAME}" EXIT
  155. # If config not explicitly set, then use defaults
  156. if [[ -z "${CONFIG}" ]]; then
  157. # Kubernetes 1.15+
  158. CONFIG=${DEFAULT_CLUSTER_YAML}
  159. # Configure the cluster IP Family only for default configs
  160. if [ "${IP_FAMILY}" = "ipv6" ]; then
  161. grep 'ipFamily: ipv6' "${CONFIG}" || \
  162. cat <<EOF >> "${CONFIG}"
  163. networking:
  164. ipFamily: ipv6
  165. EOF
  166. fi
  167. fi
  168. # Create KinD cluster
  169. if ! (kind create cluster --name="${NAME}" --config "${CONFIG}" -v9 --retain --image "${IMAGE}" --wait=60s); then
  170. echo "Could not setup KinD environment. Something wrong with KinD setup. Exporting logs."
  171. exit 1
  172. fi
  173. # If metrics server configuration directory is specified then deploy in
  174. # the cluster just created
  175. if [[ -n ${METRICS_SERVER_CONFIG_DIR} ]]; then
  176. kubectl apply -f "${METRICS_SERVER_CONFIG_DIR}"
  177. fi
  178. # Install Metallb if not set to install explicitly
  179. if [[ "${NOMETALBINSTALL}" != "true" ]]; then
  180. install_metallb ""
  181. fi
  182. # Install Cron logger if set to install explicitly'
  183. if [[ "${CRON_LOGGER_INSTALL}" == "true" ]]; then
  184. install_cron_logger ""
  185. fi
  186. }
  187. ###############################################################################
  188. #################### MULTICLUSTER SECTION ###############################
  189. ###############################################################################
  190. # Cleans up the clusters created by setup_kind_clusters
  191. # It expects CLUSTER_NAMES to be present which means that
  192. # load_cluster_topology must be called before invoking it
  193. function cleanup_kind_clusters() {
  194. echo "Test exited with exit code $?."
  195. for c in "${CLUSTER_NAMES[@]}"; do
  196. cleanup_kind_cluster "${c}"
  197. done
  198. }
  199. # setup_kind_clusters sets up a given number of kind clusters with given topology
  200. # as specified in cluster topology configuration file.
  201. # 1. IMAGE = docker image used as node by KinD
  202. # 2. IP_FAMILY = either ipv4 or ipv6
  203. #
  204. # NOTE: Please call load_cluster_topology before calling this method as it expects
  205. # cluster topology information to be loaded in advance
  206. function setup_kind_clusters() {
  207. IMAGE="${1:-"${DEFAULT_KIND_IMAGE}"}"
  208. KUBECONFIG_DIR="${ARTIFACTS:-$(mktemp -d)}/kubeconfig"
  209. IP_FAMILY="${2:-ipv4}"
  210. check_default_cluster_yaml
  211. # Trap replaces any previous trap's, so we need to explicitly cleanup both clusters here
  212. trap cleanup_kind_clusters EXIT
  213. function deploy_kind() {
  214. IDX="${1}"
  215. CLUSTER_NAME="${CLUSTER_NAMES[$IDX]}"
  216. CLUSTER_POD_SUBNET="${CLUSTER_POD_SUBNETS[$IDX]}"
  217. CLUSTER_SVC_SUBNET="${CLUSTER_SVC_SUBNETS[$IDX]}"
  218. CLUSTER_YAML="${ARTIFACTS}/config-${CLUSTER_NAME}.yaml"
  219. if [ ! -f "${CLUSTER_YAML}" ]; then
  220. cp "${DEFAULT_CLUSTER_YAML}" "${CLUSTER_YAML}"
  221. cat <<EOF >> "${CLUSTER_YAML}"
  222. networking:
  223. podSubnet: ${CLUSTER_POD_SUBNET}
  224. serviceSubnet: ${CLUSTER_SVC_SUBNET}
  225. EOF
  226. fi
  227. CLUSTER_KUBECONFIG="${KUBECONFIG_DIR}/${CLUSTER_NAME}"
  228. # Create the clusters.
  229. KUBECONFIG="${CLUSTER_KUBECONFIG}" setup_kind_cluster "${CLUSTER_NAME}" "${IMAGE}" "${CLUSTER_YAML}" "true" "true"
  230. # Kind currently supports getting a kubeconfig for internal or external usage. To simplify our tests,
  231. # its much simpler if we have a single kubeconfig that can be used internally and externally.
  232. # To do this, we can replace the server with the IP address of the docker container
  233. # https://github.com/kubernetes-sigs/kind/issues/1558 tracks this upstream
  234. CONTAINER_IP=$(docker inspect "${CLUSTER_NAME}-control-plane" --format "{{ .NetworkSettings.Networks.kind.IPAddress }}")
  235. kind get kubeconfig --name "${CLUSTER_NAME}" --internal | \
  236. sed "s/${CLUSTER_NAME}-control-plane/${CONTAINER_IP}/g" > "${CLUSTER_KUBECONFIG}"
  237. # Enable core dumps
  238. docker exec "${CLUSTER_NAME}"-control-plane bash -c "sysctl -w kernel.core_pattern=/var/lib/istio/data/core.proxy && ulimit -c unlimited"
  239. }
  240. # Now deploy the specified number of KinD clusters and
  241. # wait till they are provisioned successfully.
  242. declare -a DEPLOY_KIND_JOBS
  243. for i in "${!CLUSTER_NAMES[@]}"; do
  244. deploy_kind "${i}" & DEPLOY_KIND_JOBS+=("${!}")
  245. done
  246. for pid in "${DEPLOY_KIND_JOBS[@]}"; do
  247. wait "${pid}" || exit 1
  248. done
  249. # Install MetalLB for LoadBalancer support. Must be done synchronously since METALLB_IPS is shared.
  250. # and keep track of the list of Kubeconfig files that will be exported later
  251. export KUBECONFIGS
  252. for CLUSTER_NAME in "${CLUSTER_NAMES[@]}"; do
  253. KUBECONFIG_FILE="${KUBECONFIG_DIR}/${CLUSTER_NAME}"
  254. if [[ ${NUM_CLUSTERS} -gt 1 ]]; then
  255. install_metallb "${KUBECONFIG_FILE}"
  256. # Install Cron logger if set to install explicitly'
  257. if [[ -n ${CRON_LOGGER_INSTALL} ]]; then
  258. install_cron_logger "${KUBECONFIG_FILE}"
  259. fi
  260. fi
  261. KUBECONFIGS+=("${KUBECONFIG_FILE}")
  262. done
  263. ITER_END=$((NUM_CLUSTERS-1))
  264. for i in $(seq 0 "$ITER_END"); do
  265. for j in $(seq 0 "$ITER_END"); do
  266. if [[ "${j}" -gt "${i}" ]]; then
  267. NETWORK_ID_I="${CLUSTER_NETWORK_ID[i]}"
  268. NETWORK_ID_J="${CLUSTER_NETWORK_ID[j]}"
  269. if [[ "$NETWORK_ID_I" == "$NETWORK_ID_J" ]]; then
  270. POD_TO_POD_AND_SERVICE_CONNECTIVITY=1
  271. else
  272. POD_TO_POD_AND_SERVICE_CONNECTIVITY=0
  273. fi
  274. connect_kind_clusters \
  275. "${CLUSTER_NAMES[i]}" "${KUBECONFIGS[i]}" \
  276. "${CLUSTER_NAMES[j]}" "${KUBECONFIGS[j]}" \
  277. "${POD_TO_POD_AND_SERVICE_CONNECTIVITY}"
  278. fi
  279. done
  280. done
  281. }
  282. function connect_kind_clusters() {
  283. C1="${1}"
  284. C1_KUBECONFIG="${2}"
  285. C2="${3}"
  286. C2_KUBECONFIG="${4}"
  287. POD_TO_POD_AND_SERVICE_CONNECTIVITY="${5}"
  288. C1_NODE="${C1}-control-plane"
  289. C2_NODE="${C2}-control-plane"
  290. C1_DOCKER_IP=$(docker inspect -f "{{ .NetworkSettings.Networks.kind.IPAddress }}" "${C1_NODE}")
  291. C2_DOCKER_IP=$(docker inspect -f "{{ .NetworkSettings.Networks.kind.IPAddress }}" "${C2_NODE}")
  292. if [ "${POD_TO_POD_AND_SERVICE_CONNECTIVITY}" -eq 1 ]; then
  293. # Set up routing rules for inter-cluster direct pod to pod & service communication
  294. C1_POD_CIDR=$(KUBECONFIG="${C1_KUBECONFIG}" kubectl get node -ojsonpath='{.items[0].spec.podCIDR}')
  295. C2_POD_CIDR=$(KUBECONFIG="${C2_KUBECONFIG}" kubectl get node -ojsonpath='{.items[0].spec.podCIDR}')
  296. C1_SVC_CIDR=$(KUBECONFIG="${C1_KUBECONFIG}" kubectl cluster-info dump | sed -n 's/^.*--service-cluster-ip-range=\([^"]*\).*$/\1/p' | head -n 1)
  297. C2_SVC_CIDR=$(KUBECONFIG="${C2_KUBECONFIG}" kubectl cluster-info dump | sed -n 's/^.*--service-cluster-ip-range=\([^"]*\).*$/\1/p' | head -n 1)
  298. docker exec "${C1_NODE}" ip route add "${C2_POD_CIDR}" via "${C2_DOCKER_IP}"
  299. docker exec "${C1_NODE}" ip route add "${C2_SVC_CIDR}" via "${C2_DOCKER_IP}"
  300. docker exec "${C2_NODE}" ip route add "${C1_POD_CIDR}" via "${C1_DOCKER_IP}"
  301. docker exec "${C2_NODE}" ip route add "${C1_SVC_CIDR}" via "${C1_DOCKER_IP}"
  302. fi
  303. # Set up routing rules for inter-cluster pod to MetalLB LoadBalancer communication
  304. connect_metallb "$C1_NODE" "$C2_KUBECONFIG" "$C2_DOCKER_IP"
  305. connect_metallb "$C2_NODE" "$C1_KUBECONFIG" "$C1_DOCKER_IP"
  306. }
  307. function install_kind() {
  308. KIND_DIR=$1
  309. KIND_VERSION=$2
  310. echo 'Installing kind...'
  311. mkdir -p "${KIND_DIR}"
  312. if [[ "${MACHINE}" == "Linux" ]]; then
  313. curl -sSLo "${KIND_DIR}/kind" "https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64"
  314. elif [[ "${MACHINE}" == "Mac" ]]; then
  315. curl -sSLo "${KIND_DIR}/kind" "https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-darwin-amd64"
  316. else
  317. echo "Error Download kind ..."
  318. exit 1
  319. fi
  320. chmod +x "${KIND_DIR}/kind"
  321. }
  322. function install_kubectl() {
  323. KUBECTL_DIR=$1
  324. KUBECTL_VERSION=$2
  325. echo 'Installing kubectl...'
  326. mkdir -p "${KUBECTL_DIR}"
  327. if [[ "${MACHINE}" == "Linux" ]]; then
  328. curl -sSLo "${KUBECTL_DIR}/kubectl" "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
  329. elif [[ "${MACHINE}" == "Mac" ]]; then
  330. curl -sSLo "${KUBECTL_DIR}/kubectl" "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/darwin/amd64/kubectl"
  331. else
  332. echo "Error Download kubectl ..."
  333. exit 1
  334. fi
  335. chmod +x "${KUBECTL_DIR}/kubectl"
  336. }
  337. function install_helm() {
  338. HELM_DIR=$1
  339. HELM_VERSION=$2
  340. echo 'Installing helm...'
  341. mkdir -p "${HELM_DIR}"
  342. OS_NAME="unknown"
  343. if [[ "${MACHINE}" == "Linux" ]]; then
  344. OS_NAME="linux"
  345. elif [[ "${MACHINE}" == "Mac" ]]; then
  346. OS_NAME="darwin"
  347. else
  348. echo "Error Download helm ..."
  349. exit 1
  350. fi
  351. curl -sSLo "${HELM_DIR}/helm.tar.gz" "https://get.helm.sh/helm-${HELM_VERSION}-${OS_NAME}-amd64.tar.gz"
  352. tar zxvf "${HELM_DIR}/helm.tar.gz" -C "${HELM_DIR}"
  353. mv "${HELM_DIR}/${OS_NAME}-amd64/helm" "${HELM_DIR}"
  354. chmod +x "${HELM_DIR}/helm"
  355. }
  356. function install_metallb() {
  357. KUBECONFIG="${1}"
  358. kubectl apply --kubeconfig="$KUBECONFIG" -f "${ROOT}/build/config/metallb.yaml"
  359. kubectl create --kubeconfig="$KUBECONFIG" secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
  360. if [ -z "${METALLB_IPS[*]-}" ]; then
  361. # Take IPs from the end of the docker kind network subnet to use for MetalLB IPs
  362. DOCKER_KIND_SUBNET="$(docker inspect kind | jq '.[0].IPAM.Config[0].Subnet' -r)"
  363. METALLB_IPS=()
  364. while read -r ip; do
  365. METALLB_IPS+=("$ip")
  366. done < <(cidr_to_ips "$DOCKER_KIND_SUBNET" | tail -n 100)
  367. fi
  368. # Give this cluster of those IPs
  369. RANGE="${METALLB_IPS[0]}-${METALLB_IPS[9]}"
  370. METALLB_IPS=("${METALLB_IPS[@]:10}")
  371. echo 'apiVersion: v1
  372. kind: ConfigMap
  373. metadata:
  374. namespace: metallb-system
  375. name: config
  376. data:
  377. config: |
  378. address-pools:
  379. - name: default
  380. protocol: layer2
  381. addresses:
  382. - '"$RANGE" | kubectl apply --kubeconfig="$KUBECONFIG" -f -
  383. }
  384. function install_cron_logger() {
  385. KUBECONFIG="${1}"
  386. kubectl apply --kubeconfig="$KUBECONFIG" -f "${ROOT}/build/config/logging/"
  387. }
  388. function connect_metallb() {
  389. REMOTE_NODE=$1
  390. METALLB_KUBECONFIG=$2
  391. METALLB_DOCKER_IP=$3
  392. IP_REGEX='(([0-9]{1,3}\.?){4})'
  393. LB_CONFIG="$(kubectl --kubeconfig="${METALLB_KUBECONFIG}" -n metallb-system get cm config -o jsonpath="{.data.config}")"
  394. if [[ "$LB_CONFIG" =~ $IP_REGEX-$IP_REGEX ]]; then
  395. while read -r lb_cidr; do
  396. docker exec "${REMOTE_NODE}" ip route add "${lb_cidr}" via "${METALLB_DOCKER_IP}"
  397. done < <(ips_to_cidrs "${BASH_REMATCH[1]}" "${BASH_REMATCH[3]}")
  398. fi
  399. }
  400. function cidr_to_ips() {
  401. CIDR="$1"
  402. python3 - <<EOF
  403. from ipaddress import IPv4Network; [print(str(ip)) for ip in IPv4Network('$CIDR').hosts()]
  404. EOF
  405. }
  406. function ips_to_cidrs() {
  407. IP_RANGE_START="$1"
  408. IP_RANGE_END="$2"
  409. python3 - <<EOF
  410. from ipaddress import summarize_address_range, IPv4Address
  411. [ print(n.compressed) for n in summarize_address_range(IPv4Address(u'$IP_RANGE_START'), IPv4Address(u'$IP_RANGE_END')) ]
  412. EOF
  413. }