forked from suse-edge/Factory
296 lines
10 KiB
Bash
296 lines
10 KiB
Bash
#!/usr/bin/bash
|
|
|
|
set -euxo pipefail
|
|
|
|
# Export IRONIC_IP to avoid needing to lean on IRONIC_URL_HOST for consumption in
|
|
# e.g. dnsmasq configuration
|
|
export IRONIC_IP="${IRONIC_IP:-}"
|
|
IRONIC_IPV6="${IRONIC_IPV6:-}"
|
|
PROVISIONING_INTERFACE="${PROVISIONING_INTERFACE:-}"
|
|
PROVISIONING_IP="${PROVISIONING_IP:-}"
|
|
PROVISIONING_MACS="${PROVISIONING_MACS:-}"
|
|
IRONIC_URL_HOSTNAME="${IRONIC_URL_HOSTNAME:-}"
|
|
IPXE_CUSTOM_FIRMWARE_DIR="${IPXE_CUSTOM_FIRMWARE_DIR:-/shared/custom_ipxe_firmware}"
|
|
CUSTOM_CONFIG_DIR="${CUSTOM_CONFIG_DIR:-/conf}"
|
|
CUSTOM_DATA_DIR="${CUSTOM_DATA_DIR:-/data}"
|
|
export DNSMASQ_CONF_DIR="${CUSTOM_CONFIG_DIR}/dnsmasq"
|
|
export DNSMASQ_DATA_DIR="${CUSTOM_DATA_DIR}/dnsmasq"
|
|
export DNSMASQ_TEMP_DIR="${CUSTOM_CONFIG_DIR}/dnsmasq"
|
|
export HTTPD_DIR="${CUSTOM_CONFIG_DIR}/httpd"
|
|
export HTTPD_CONF_DIR="${HTTPD_DIR}/conf"
|
|
export HTTPD_CONF_DIR_D="${HTTPD_DIR}/conf.d"
|
|
export IRONIC_CONF_DIR="${CUSTOM_CONFIG_DIR}/ironic"
|
|
export IRONIC_DB_DIR="${CUSTOM_DATA_DIR}/db"
|
|
export IRONIC_GEN_CERT_DIR="${CUSTOM_DATA_DIR}/auto_gen_certs"
|
|
export IRONIC_TMP_DATA_DIR="${CUSTOM_DATA_DIR}/tmp"
|
|
export PROBE_CONF_DIR="${CUSTOM_CONFIG_DIR}/probes"
|
|
|
|
mkdir -p "${IRONIC_CONF_DIR}" "${PROBE_CONF_DIR}" "${HTTPD_CONF_DIR}" \
|
|
"${HTTPD_CONF_DIR_D}" "${DNSMASQ_CONF_DIR}" "${DNSMASQ_TEMP_DIR}" \
|
|
"${IRONIC_DB_DIR}" "${IRONIC_GEN_CERT_DIR}" "${DNSMASQ_DATA_DIR}" \
|
|
"${IRONIC_TMP_DATA_DIR}"
|
|
|
|
export HTPASSWD_FILE="${IRONIC_CONF_DIR}/htpasswd"
|
|
export LOCAL_DB_URI="sqlite:///${IRONIC_DB_DIR}/ironic.sqlite"
|
|
|
|
export IRONIC_USE_MARIADB=${IRONIC_USE_MARIADB:-false}
|
|
|
|
|
|
get_ip_of_hostname()
|
|
{
|
|
if [[ "$#" -ne 2 ]]; then
|
|
echo "${FUNCNAME}: two parameters required, $# provided" >&2
|
|
return 1
|
|
fi
|
|
|
|
case $2 in
|
|
4)
|
|
QUERY="a";;
|
|
6)
|
|
QUERY="aaaa";;
|
|
*)
|
|
echo "${FUNCNAME}: the second parameter should be [a|aaaa] for A and AAAA records"
|
|
return 1;;
|
|
esac
|
|
|
|
local HOSTNAME=$1
|
|
|
|
echo $(nslookup -type=${QUERY} "${HOSTNAME}" | tail -n2 | grep -w "Address:" | cut -d " " -f2)
|
|
}
|
|
|
|
get_interface_of_ip()
|
|
{
|
|
local IP_VERS=""
|
|
|
|
if [[ "$#" -gt 2 ]]; then
|
|
echo "${FUNCNAME}: too many parameters" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$#" -eq 2 ]]; then
|
|
case $2 in
|
|
4|6)
|
|
local IP_VERS="-${2}"
|
|
;;
|
|
*)
|
|
echo "${FUNCNAME}: the second parameter should be [4|6] (or missing for both)" >&2
|
|
return 2
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
local IP_ADDR=$1
|
|
|
|
# Convert the address using ipcalc which strips out the subnet.
|
|
# For IPv6 addresses, this will give the short-form address
|
|
IP_ADDR="$(ipcalc "${IP_ADDR}" | grep "^Address:" | awk '{print $2}')"
|
|
|
|
echo $(ip ${IP_VERS} -br addr show scope global | grep -i " ${IP_ADDR}/" | cut -f 1 -d ' ' | cut -f 1 -d '@')
|
|
}
|
|
|
|
get_ip_of_interface()
|
|
{
|
|
local IP_VERS=""
|
|
|
|
if [[ "$#" -gt 2 ]]; then
|
|
echo "${FUNCNAME}: too many parameters" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$#" -eq 2 ]]; then
|
|
case $2 in
|
|
4|6)
|
|
local IP_VERS="-${2}"
|
|
;;
|
|
*)
|
|
echo "${FUNCNAME}: the second parameter should be [4|6] (or missing for both)" >&2
|
|
return 2
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
local IFACE=$1
|
|
|
|
echo $(ip ${IP_VERS} -br addr show scope global up dev ${IFACE} | awk '{print $3}' | sed -e 's%/.*%%' | head -n 1)
|
|
}
|
|
|
|
get_provisioning_interface()
|
|
{
|
|
if [[ -n "$PROVISIONING_INTERFACE" ]]; then
|
|
# don't override the PROVISIONING_INTERFACE if one is provided
|
|
echo "$PROVISIONING_INTERFACE"
|
|
return
|
|
fi
|
|
|
|
local interface=""
|
|
|
|
for mac in ${PROVISIONING_MACS//,/ }; do
|
|
if ip -br link show up | grep -i "$mac" &>/dev/null; then
|
|
interface="$(ip -br link show up | grep -i "$mac" | cut -f 1 -d ' ' | cut -f 1 -d '@')"
|
|
break
|
|
fi
|
|
done
|
|
|
|
echo "$interface"
|
|
}
|
|
|
|
PROVISIONING_INTERFACE="$(get_provisioning_interface)"
|
|
export PROVISIONING_INTERFACE
|
|
|
|
export LISTEN_ALL_INTERFACES="${LISTEN_ALL_INTERFACES:-true}"
|
|
|
|
# Wait for the interface or IP to be up, sets $IRONIC_IP
|
|
wait_for_interface_or_ip()
|
|
{
|
|
# If $PROVISIONING_IP is specified, then we wait for that to become
|
|
# available on an interface, otherwise we look at $PROVISIONING_INTERFACE
|
|
# for an IP
|
|
if [[ -n "${PROVISIONING_IP}" ]]; then
|
|
local IFACE_OF_IP=""
|
|
|
|
until [[ -n "$IFACE_OF_IP" ]]; do
|
|
echo "Waiting for ${PROVISIONING_IP} to be configured on an interface..."
|
|
IFACE_OF_IP="$(get_interface_of_ip "${PROVISIONING_IP}")"
|
|
sleep 1
|
|
done
|
|
|
|
echo "Found $PROVISIONING_IP on interface \"${IFACE_OF_IP}\"!"
|
|
|
|
export PROVISIONING_INTERFACE="$IFACE_OF_IP"
|
|
# If the IP contains a colon, then it's an IPv6 address
|
|
if [[ "$PROVISIONING_IP" =~ .*:.* ]]; then
|
|
export IRONIC_IPV6="$PROVISIONING_IP"
|
|
export IRONIC_IP=""
|
|
else
|
|
export IRONIC_IP="$PROVISIONING_IP"
|
|
fi
|
|
elif [[ -n "${IRONIC_IP}" ]]; then
|
|
if [[ "$IRONIC_IP" =~ .*:.* ]]; then
|
|
export IRONIC_IPV6="$IRONIC_IP"
|
|
export IRONIC_IP=""
|
|
fi
|
|
elif [[ -n "${PROVISIONING_INTERFACE}" ]]; then
|
|
until [[ -n "$IRONIC_IPV6" ]] || [[ -n "$IRONIC_IP" ]]; do
|
|
echo "Waiting for ${PROVISIONING_INTERFACE} interface to be configured..."
|
|
|
|
IRONIC_IPV6="$(get_ip_of_interface "${PROVISIONING_INTERFACE}" 6)"
|
|
sleep 1
|
|
|
|
IRONIC_IP="$(get_ip_of_interface "${PROVISIONING_INTERFACE}" 4)"
|
|
sleep 1
|
|
done
|
|
|
|
if [[ -n "$IRONIC_IPV6" ]]; then
|
|
echo "Found $IRONIC_IPV6 on interface \"${PROVISIONING_INTERFACE}\"!"
|
|
export IRONIC_IPV6
|
|
fi
|
|
if [[ -n "$IRONIC_IP" ]]; then
|
|
echo "Found $IRONIC_IP on interface \"${PROVISIONING_INTERFACE}\"!"
|
|
export IRONIC_IP
|
|
fi
|
|
elif [[ -n "$IRONIC_URL_HOSTNAME" ]]; then
|
|
local IPV6_IFACE=""
|
|
local IPV4_IFACE=""
|
|
|
|
# we should get at least one IP address
|
|
until [[ -n "$IPV6_IFACE" ]] || [[ -n "$IPV4_IFACE" ]]; do
|
|
local IPV6_RECORD=""
|
|
local IPV4_RECORD=""
|
|
|
|
IPV6_RECORD="$(get_ip_of_hostname "${IRONIC_URL_HOSTNAME}" 6)"
|
|
IPV4_RECORD="$(get_ip_of_hostname "${IRONIC_URL_HOSTNAME}" 4)"
|
|
|
|
# We couldn't get any IP
|
|
if [[ -z "$IPV4_RECORD" ]] && [[ -z "$IPV6_RECORD" ]]; then
|
|
echo "${FUNCNAME}: no valid IP found for hostname ${IRONIC_URL_HOSTNAME}" >&2
|
|
return 1
|
|
fi
|
|
|
|
echo "Waiting for ${IPV6_RECORD} to be configured on an interface"
|
|
IPV6_IFACE="$(get_interface_of_ip "${IPV6_RECORD}" 6)"
|
|
sleep 1
|
|
|
|
echo "Waiting for ${IPV4_RECORD} to be configured on an interface"
|
|
IPV4_IFACE="$(get_interface_of_ip "${IPV4_RECORD}" 4)"
|
|
sleep 1
|
|
done
|
|
|
|
# Add some debugging output
|
|
if [[ -n "$IPV6_IFACE" ]]; then
|
|
echo "Found $IPV6_RECORD on interface \"${IPV6_IFACE}\"!"
|
|
export IRONIC_IPV6="$IPV6_RECORD"
|
|
fi
|
|
if [[ -n "$IPV4_IFACE" ]]; then
|
|
echo "Found $IPV4_RECORD on interface \"${IPV4_IFACE}\"!"
|
|
export IRONIC_IP="$IPV4_RECORD"
|
|
fi
|
|
|
|
# Make sure both IPs are asigned to the same interface
|
|
if [[ -n "$IPV6_IFACE" ]] && [[ -n "$IPV4_IFACE" ]] && [[ "$IPV6_IFACE" != "$IPV4_IFACE" ]]; then
|
|
echo "Warning, the IPv4 and IPv6 addresses from \"${HOSTNAME}\" are assigned to different " \
|
|
"interfaces (\"${IPV6_IFACE}\" and \"${IPV4_IFACE}\")" >&2
|
|
fi
|
|
|
|
else
|
|
echo "Cannot determine an interface or an IP for binding and creating URLs"
|
|
return 1
|
|
fi
|
|
|
|
# Define the URLs based on the what we have found,
|
|
# prioritize IPv6 for IRONIC_URL_HOST
|
|
if [[ -n "$IRONIC_IP" ]]; then
|
|
export ENABLE_IPV4=yes
|
|
export IRONIC_URL_HOST="$IRONIC_IP"
|
|
fi
|
|
if [[ -n "$IRONIC_IPV6" ]]; then
|
|
export ENABLE_IPV6=yes
|
|
export IRONIC_URL_HOST="[${IRONIC_IPV6}]" # The HTTP host needs surrounding with brackets
|
|
fi
|
|
|
|
# Once determined if we have IPv4 and/or IPv6, override the hostname if provided
|
|
if [[ -n "$IRONIC_URL_HOSTNAME" ]]; then
|
|
IRONIC_URL_HOST=$IRONIC_URL_HOSTNAME
|
|
fi
|
|
|
|
# Avoid having to construct full URL multiple times while allowing
|
|
# the override of IRONIC_HTTP_URL for environments in which IRONIC_IP
|
|
# is unreachable from hosts being provisioned.
|
|
export IRONIC_HTTP_URL="${IRONIC_HTTP_URL:-http://${IRONIC_URL_HOST}:${HTTP_PORT}}"
|
|
export IRONIC_TFTP_URL="${IRONIC_TFTP_URL:-tftp://${IRONIC_URL_HOST}}"
|
|
export IRONIC_BASE_URL=${IRONIC_BASE_URL:-"${IRONIC_SCHEME}://${IRONIC_URL_HOST}:${IRONIC_ACCESS_PORT}"}
|
|
}
|
|
|
|
render_j2_config()
|
|
{
|
|
python3.11 -c 'import os; import sys; import jinja2; sys.stdout.write(jinja2.Template(sys.stdin.read()).render(env=os.environ))' < "$1" > "$2"
|
|
}
|
|
|
|
run_ironic_dbsync()
|
|
{
|
|
if [[ "${IRONIC_USE_MARIADB}" == "true" ]]; then
|
|
# It's possible for the dbsync to fail if mariadb is not up yet, so
|
|
# retry until success
|
|
until ironic-dbsync --config-file "${IRONIC_CONF_DIR}/ironic.conf" upgrade; do
|
|
echo "WARNING: ironic-dbsync failed, retrying"
|
|
sleep 1
|
|
done
|
|
else
|
|
# SQLite does not support some statements. Fortunately, we can just
|
|
# create the schema in one go if not already created, instead of going
|
|
# through an upgrade
|
|
cp "/var/lib/ironic/ironic.sqlite" "${IRONIC_DB_DIR}/ironic.sqlite"
|
|
DB_VERSION="$(ironic-dbsync --config-file "${IRONIC_CONF_DIR}/ironic.conf" version)"
|
|
if [[ "${DB_VERSION}" == "None" ]]; then
|
|
ironic-dbsync --config-file "${IRONIC_CONF_DIR}/ironic.conf" create_schema
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Use the special value "unix" for unix sockets
|
|
export IRONIC_PRIVATE_PORT=${IRONIC_PRIVATE_PORT:-unix}
|
|
|
|
export IRONIC_ACCESS_PORT=${IRONIC_ACCESS_PORT:-6385}
|
|
export IRONIC_LISTEN_PORT=${IRONIC_LISTEN_PORT:-$IRONIC_ACCESS_PORT}
|
|
|
|
export IRONIC_ENABLE_DISCOVERY=${IRONIC_ENABLE_DISCOVERY:-${IRONIC_INSPECTOR_ENABLE_DISCOVERY:-false}}
|