diff --git a/ironic-image/Dockerfile b/ironic-image/Dockerfile index 3f779c1..ad4238b 100644 --- a/ironic-image/Dockerfile +++ b/ironic-image/Dockerfile @@ -32,7 +32,9 @@ RUN mkdir -p /installroot/var/lib/ironic && \ /installroot/usr/bin/sqlite3 /installroot/var/lib/ironic/ironic.sqlite "pragma journal_mode=wal" && \ zypper --installroot /installroot --non-interactive remove sqlite3 +# build actual image FROM micro AS final + MAINTAINER SUSE LLC (https://www.suse.com/) # Define labels according to https://en.opensuse.org/Building_derived_containers LABEL org.opencontainers.image.title="SLE Openstack Ironic Container Image" @@ -62,14 +64,16 @@ RUN echo 'alias mkisofs="xorriso -as mkisofs"' >> ~/.bashrc COPY mkisofs_wrapper /usr/bin/mkisofs RUN set -euo pipefail; chmod +x /usr/bin/mkisofs -COPY auth-common.sh configure-ironic.sh ironic-common.sh rundnsmasq runhttpd runironic runlogwatch.sh tls-common.sh configure-nonroot.sh ironic-probe.j2 /bin/ -RUN set -euo pipefail; chmod +x /bin/auth-common.sh; chmod +x /bin/configure-ironic.sh; chmod +x /bin/ironic-common.sh; chmod +x /bin/rundnsmasq; chmod +x /bin/runhttpd; chmod +x /bin/runironic; chmod +x /bin/runlogwatch.sh; chmod +x /bin/tls-common.sh; chmod +x /bin/configure-nonroot.sh; RUN mkdir -p /tftpboot RUN mkdir -p $GRUB_DIR -# No need to support the Legacy BIOS boot -#RUN cp /usr/share/syslinux/pxelinux.0 /tftpboot -#RUN cp /usr/share/syslinux/chain.c32 /tftpboot/ +COPY scripts/ /bin/ +COPY configure-nonroot.sh /bin/ +RUN set -euo pipefail; chmod +x /bin/configure-ironic.sh /bin/rundnsmasq /bin/runhttpd /bin/runironic /bin/runironic-exporter /bin/runlogwatch.sh /bin/configure-nonroot.sh + +COPY ironic-config/inspector.ipxe.j2 ironic-config/httpd-ironic-api.conf.j2 \ + ironic-config/ipxe_config.template ironic-config/dnsmasq.conf.j2 \ + /templates/ # IRONIC # RUN cp /usr/share/ipxe/undionly.kpxe /tftpboot/undionly.kpxe @@ -85,23 +89,17 @@ RUN if [ "$(uname -m)" = "aarch64" ]; then\ COPY --from=base /tmp/esp-x86_64.img /tmp/uefi_esp-x86_64.img COPY --from=base /tmp/esp-aarch64.img /tmp/uefi_esp-arm64.img -COPY ironic.conf.j2 /etc/ironic/ -COPY inspector.ipxe.j2 httpd-ironic-api.conf.j2 ipxe_config.template /tmp/ -COPY network-data-schema-empty.json /etc/ironic/ - -# DNSMASQ -COPY dnsmasq.conf.j2 /etc/ - -# Custom httpd config, removes all but the bare minimum needed modules -COPY httpd.conf.j2 /etc/httpd/conf/ -COPY httpd-modules.conf /etc/httpd/conf.modules.d/ -COPY apache2-vmedia.conf.j2 /etc/httpd-vmedia.conf.j2 -COPY apache2-ipxe.conf.j2 /etc/httpd-ipxe.conf.j2 +COPY ironic-config/ironic.conf.j2 ironic-config/network-data-schema-empty.json /etc/ironic/ # Workaround # Removing the 010-ironic.conf file that comes with the package RUN rm /etc/ironic/ironic.conf.d/010-ironic.conf +# Custom httpd config, removes all but the bare minimum needed modules +COPY ironic-config/httpd.conf.j2 /etc/httpd/conf/ +COPY ironic-config/httpd-modules.conf /etc/httpd/conf.modules.d/ +COPY ironic-config/apache2-vmedia.conf.j2 /templates/httpd-vmedia.conf.j2 +COPY ironic-config/apache2-ipxe.conf.j2 /templates/httpd-ipxe.conf.j2 + # configure non-root user and set relevant permissions -RUN configure-nonroot.sh && \ - rm -f /bin/configure-nonroot.sh +RUN configure-nonroot.sh && rm -f /bin/configure-nonroot.sh diff --git a/ironic-image/auth-common.sh b/ironic-image/auth-common.sh deleted file mode 100644 index cb6a548..0000000 --- a/ironic-image/auth-common.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/bash - -set -euxo pipefail - -export IRONIC_REVERSE_PROXY_SETUP=${IRONIC_REVERSE_PROXY_SETUP:-false} - -# Backward compatibility -if [[ "${IRONIC_DEPLOYMENT:-}" == "Conductor" ]]; then - export IRONIC_EXPOSE_JSON_RPC=true -else - export IRONIC_EXPOSE_JSON_RPC="${IRONIC_EXPOSE_JSON_RPC:-false}" -fi - -IRONIC_HTPASSWD_FILE=/etc/ironic/htpasswd -if [[ -f "/auth/ironic/htpasswd" ]]; then - IRONIC_HTPASSWD=$( "${IRONIC_HTPASSWD_FILE}-rpc" - fi -} - -configure_ironic_auth() -{ - local config=/etc/ironic/ironic.conf - # Configure HTTP basic auth for API server - if [[ -n "${IRONIC_HTPASSWD}" ]]; then - printf "%s\n" "${IRONIC_HTPASSWD}" > "${IRONIC_HTPASSWD_FILE}" - if [[ "${IRONIC_REVERSE_PROXY_SETUP}" == "false" ]]; then - crudini --set "${config}" DEFAULT auth_strategy http_basic - crudini --set "${config}" DEFAULT http_basic_auth_user_file "${IRONIC_HTPASSWD_FILE}" - fi - fi -} - -write_htpasswd_files() -{ - if [[ -n "${IRONIC_HTPASSWD:-}" ]]; then - printf "%s\n" "${IRONIC_HTPASSWD}" > "${IRONIC_HTPASSWD_FILE}" - fi -} diff --git a/ironic-image/inspector.ipxe.j2 b/ironic-image/inspector.ipxe.j2 deleted file mode 100644 index c105178..0000000 --- a/ironic-image/inspector.ipxe.j2 +++ /dev/null @@ -1,10 +0,0 @@ -#!ipxe - -:retry_boot -echo In inspector.ipxe -imgfree -# NOTE(dtantsur): keep inspection kernel params in [mdns]params in -# ironic-inspector-image and configuration in configure-ironic.sh -kernel --timeout 60000 http://{{ env.IRONIC_URL_HOST }}:{{ env.HTTP_PORT }}/images/ironic-python-agent-${buildarch}.kernel ipa-insecure=1 ipa-inspection-collectors={{ env.IRONIC_IPA_COLLECTORS }} systemd.journald.forward_to_console=yes BOOTIF=${mac} ipa-debug=1 ipa-enable-vlan-interfaces={{ env.IRONIC_ENABLE_VLAN_INTERFACES }} ipa-inspection-dhcp-all-interfaces=1 ipa-collect-lldp=1 {{ env.INSPECTOR_EXTRA_ARGS }} initrd=ironic-python-agent.initramfs {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} || goto retry_boot -initrd --timeout 60000 http://{{ env.IRONIC_URL_HOST }}:{{ env.HTTP_PORT }}/images/ironic-python-agent-${buildarch}.initramfs || goto retry_boot -boot diff --git a/ironic-image/apache2-ipxe.conf.j2 b/ironic-image/ironic-config/apache2-ipxe.conf.j2 similarity index 100% rename from ironic-image/apache2-ipxe.conf.j2 rename to ironic-image/ironic-config/apache2-ipxe.conf.j2 diff --git a/ironic-image/apache2-vmedia.conf.j2 b/ironic-image/ironic-config/apache2-vmedia.conf.j2 similarity index 68% rename from ironic-image/apache2-vmedia.conf.j2 rename to ironic-image/ironic-config/apache2-vmedia.conf.j2 index fd87d39..20dc4a7 100644 --- a/ironic-image/apache2-vmedia.conf.j2 +++ b/ironic-image/ironic-config/apache2-vmedia.conf.j2 @@ -10,15 +10,13 @@ Listen {{ env.VMEDIA_TLS_PORT }} SSLCertificateFile {{ env.IRONIC_VMEDIA_CERT_FILE }} SSLCertificateKeyFile {{ env.IRONIC_VMEDIA_KEY_FILE }} - - AllowOverride None - Require all granted + + Order deny,allow + deny from all - - - Options Indexes FollowSymLinks - AllowOverride None - Require all granted + + Order allow,deny + allow from all diff --git a/ironic-image/dnsmasq.conf.j2 b/ironic-image/ironic-config/dnsmasq.conf.j2 similarity index 82% rename from ironic-image/dnsmasq.conf.j2 rename to ironic-image/ironic-config/dnsmasq.conf.j2 index ad58078..f216fed 100644 --- a/ironic-image/dnsmasq.conf.j2 +++ b/ironic-image/ironic-config/dnsmasq.conf.j2 @@ -3,6 +3,7 @@ bind-dynamic enable-tftp tftp-root=/shared/tftpboot log-queries +dhcp-leasefile=/data/dnsmasq/dnsmasq.leases # Configure listening for DNS (0 disables DNS) port={{ env.DNS_PORT }} @@ -31,11 +32,11 @@ dhcp-match=ipxe,175 # Client is already running iPXE; move to next stage of chainloading {%- if env.IPXE_TLS_SETUP == "true" %} # iPXE with (U)EFI -dhcp-boot=tag:efi,tag:ipxe,http://{{ env.IRONIC_URL_HOST }}:{{ env.HTTP_PORT }}/custom-ipxe/snponly.efi +dhcp-boot=tag:efi,tag:ipxe,{{ env.IRONIC_HTTP_URL }}/custom-ipxe/snponly.efi # iPXE with BIOS -dhcp-boot=tag:ipxe,http://{{ env.IRONIC_URL_HOST }}:{{ env.HTTP_PORT }}/custom-ipxe/undionly.kpxe +dhcp-boot=tag:ipxe,{{ env.IRONIC_HTTP_URL }}/custom-ipxe/undionly.kpxe {% else %} -dhcp-boot=tag:ipxe,http://{{ env.IRONIC_URL_HOST }}:{{ env.HTTP_PORT }}/boot.ipxe +dhcp-boot=tag:ipxe,{{ env.IRONIC_HTTP_URL }}/boot.ipxe {% endif %} # Note: Need to test EFI booting @@ -59,8 +60,8 @@ ra-param={{ env.PROVISIONING_INTERFACE }},0,0 dhcp-vendorclass=set:pxe6,enterprise:343,PXEClient dhcp-userclass=set:ipxe6,iPXE -dhcp-option=tag:pxe6,option6:bootfile-url,tftp://{{ env.IRONIC_URL_HOST }}/snponly.efi -dhcp-option=tag:ipxe6,option6:bootfile-url,http://{{ env.IRONIC_URL_HOST }}:{{ env.HTTP_PORT }}/boot.ipxe +dhcp-option=tag:pxe6,option6:bootfile-url,{{ env.IRONIC_TFTP_URL }}/snponly.efi +dhcp-option=tag:ipxe6,option6:bootfile-url,{{ env.IRONIC_HTTP_URL }}/boot.ipxe # It can be used when setting DNS or GW variables. {%- if env["GATEWAY_IP"] is undefined %} diff --git a/ironic-image/httpd-ironic-api.conf.j2 b/ironic-image/ironic-config/httpd-ironic-api.conf.j2 similarity index 97% rename from ironic-image/httpd-ironic-api.conf.j2 rename to ironic-image/ironic-config/httpd-ironic-api.conf.j2 index 15c73b6..317118e 100644 --- a/ironic-image/httpd-ironic-api.conf.j2 +++ b/ironic-image/ironic-config/httpd-ironic-api.conf.j2 @@ -45,7 +45,7 @@ Listen {{ env.IRONIC_URL_HOST }}:{{ env.IRONIC_LISTEN_PORT }} {% if "IRONIC_HTPASSWD" in env and env.IRONIC_HTPASSWD | length %} AuthType Basic AuthName "Restricted area" - AuthUserFile "/etc/ironic/htpasswd" + AuthUserFile {{ env.HTPASSWD_FILE }} Require valid-user {% endif %} diff --git a/ironic-image/httpd-modules.conf b/ironic-image/ironic-config/httpd-modules.conf similarity index 100% rename from ironic-image/httpd-modules.conf rename to ironic-image/ironic-config/httpd-modules.conf diff --git a/ironic-image/httpd.conf.j2 b/ironic-image/ironic-config/httpd.conf.j2 similarity index 94% rename from ironic-image/httpd.conf.j2 rename to ironic-image/ironic-config/httpd.conf.j2 index 28e5308..e21f536 100644 --- a/ironic-image/httpd.conf.j2 +++ b/ironic-image/ironic-config/httpd.conf.j2 @@ -1,10 +1,10 @@ -ServerRoot "/etc/httpd" +ServerRoot {{ env.HTTPD_DIR }} {%- if env.LISTEN_ALL_INTERFACES | lower == "true" %} Listen {{ env.HTTP_PORT }} {% else %} Listen {{ env.IRONIC_URL_HOST }}:{{ env.HTTP_PORT }} {% endif %} -Include conf.modules.d/*.conf +Include /etc/httpd/conf.modules.d/*.conf User ironic-suse Group ironic-suse @@ -64,7 +64,7 @@ AddDefaultCharset UTF-8 MIMEMagicFile conf/magic -PidFile /var/tmp/httpd.pid +PidFile {{ env.IRONIC_TMP_DATA_DIR }}/httpd.pid # EnableSendfile directive could speed up deployments but it could also cause # issues depending on the underlying file system, to learn more: diff --git a/ironic-image/ironic-config/inspector.ipxe.j2 b/ironic-image/ironic-config/inspector.ipxe.j2 new file mode 100644 index 0000000..dd47aa9 --- /dev/null +++ b/ironic-image/ironic-config/inspector.ipxe.j2 @@ -0,0 +1,10 @@ +#!ipxe + +:retry_boot +echo In inspector.ipxe +imgfree +# NOTE(dtantsur): keep inspection kernel params in [mdns]params in +# ironic-inspector-image and configuration in configure-ironic.sh +kernel --timeout 60000 {{ env.IRONIC_HTTP_URL }}/images/ironic-python-agent-${buildarch}.kernel ipa-insecure=1 ipa-inspection-collectors={{ env.IRONIC_IPA_COLLECTORS }} systemd.journald.forward_to_console=yes BOOTIF=${mac} ipa-debug=1 ipa-enable-vlan-interfaces={{ env.IRONIC_ENABLE_VLAN_INTERFACES }} ipa-inspection-dhcp-all-interfaces=1 ipa-collect-lldp=1 {{ env.INSPECTOR_EXTRA_ARGS }} initrd=ironic-python-agent.initramfs {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} || goto retry_boot +initrd --timeout 60000 {{ env.IRONIC_HTTP_URL }}/images/ironic-python-agent.initramfs || goto retry_boot +boot diff --git a/ironic-image/ipxe_config.template b/ironic-image/ironic-config/ipxe_config.template similarity index 100% rename from ironic-image/ipxe_config.template rename to ironic-image/ironic-config/ipxe_config.template diff --git a/ironic-image/ironic.conf.j2 b/ironic-image/ironic-config/ironic.conf.j2 similarity index 81% rename from ironic-image/ironic.conf.j2 rename to ironic-image/ironic-config/ironic.conf.j2 index 58bc69d..3813275 100644 --- a/ironic-image/ironic.conf.j2 +++ b/ironic-image/ironic-config/ironic.conf.j2 @@ -49,6 +49,7 @@ deploy_logs_local_path = /shared/log/ironic/deploy # retries here works around such problems without affecting the normal path. # See https://bugzilla.redhat.com/show_bug.cgi?id=1822763 max_command_attempts = 30 +certificates_path = {{ env.IRONIC_GEN_CERT_DIR }} [api] {% if env.IRONIC_REVERSE_PROXY_SETUP == "true" %} @@ -83,7 +84,7 @@ send_sensor_data = {{ env.SEND_SENSOR_DATA }} # Power state is checked every 60 seconds and BMC activity should # be avoided more often than once every sixty seconds. send_sensor_data_interval = 160 -bootloader = {{ env.IRONIC_BOOT_BASE_URL }}/uefi_esp-{{ env.DEPLOY_ARCHITECTURE }}.img +bootloader = file:///templates/uefi_esp.img verify_step_priority_override = management.clear_job_queue:90 # We don't use this feature, and it creates an additional load on the database node_history = False @@ -95,16 +96,22 @@ deploy_kernel = file://{{ env.IRONIC_DEFAULT_KERNEL }} {% if env.IRONIC_DEFAULT_RAMDISK is defined %} deploy_ramdisk = file://{{ env.IRONIC_DEFAULT_RAMDISK }} {% endif %} +{% if env.DISABLE_DEEP_IMAGE_INSPECTION | lower == "true" %} +disable_deep_image_inspection = True +{% endif %} +# Allowed path for file:// links: ipa-downloader uses /shared/html/images, +# while the bootloader configuration above refers to /templates. +file_url_allowed_paths = /shared/html/images,/templates [database] -{% if env.IRONIC_USE_MARIADB | lower == "false" %} -connection = sqlite:////var/lib/ironic/ironic.sqlite +{% if env.IRONIC_USE_MARIADB | lower == "true" %} +connection = {{ env.MARIADB_CONNECTION }} +{% else %} +connection = {{ env.LOCAL_DB_URI }} # Synchronous mode is required for data integrity in case of operating system # crash. In our case we restart the container from scratch, so we can save some # IO by not doing syncs all the time. sqlite_synchronous = False -{% else %} -connection = {{ env.MARIADB_CONNECTION }} {% endif %} [deploy] @@ -112,7 +119,7 @@ default_boot_option = local erase_devices_metadata_priority = 10 erase_devices_priority = 0 http_root = /shared/html/ -http_url = {{ env.IRONIC_BOOT_BASE_URL }} +http_url = {{ env.IRONIC_HTTP_URL }} fast_track = {{ env.IRONIC_FAST_TRACK }} {% if env.IRONIC_BOOT_ISO_SOURCE %} ramdisk_image_download_source = {{ env.IRONIC_BOOT_ISO_SOURCE }} @@ -175,7 +182,7 @@ cipher_suite_versions = 3,17 # unauthenticated connections from other processes in the same host since the # containers are in host networking. auth_strategy = http_basic -http_basic_auth_user_file = /etc/ironic/htpasswd-rpc +http_basic_auth_user_file = {{ env.IRONIC_RPC_HTPASSWD_FILE }} host_ip = {% if env.LISTEN_ALL_INTERFACES | lower == "true" %}::{% else %}{{ env.IRONIC_IP }}{% endif %} {% if env.IRONIC_TLS_SETUP == "true" %} use_ssl = true @@ -201,7 +208,7 @@ images_path = /shared/html/tmp instance_master_path = /shared/html/master_images tftp_master_path = /shared/tftpboot/master_images tftp_root = /shared/tftpboot -kernel_append_params = nofb nomodeset vga=normal ipa-insecure={{ env.IPA_INSECURE }} {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console=yes net.ifnames={{ '0' if env.PREDICTABLE_NIC_NAMES == 'false' else '1' }} +kernel_append_params = nofb nomodeset vga=normal ipa-insecure=1 {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console=yes # This makes networking boot templates generated even for nodes using local # boot (the default), ensuring that they boot correctly even if they start # netbooting for some reason (e.g. with the noop management interface). @@ -209,19 +216,19 @@ enable_netboot_fallback = true # Enable the fallback path to in-band inspection ipxe_fallback_script = inspector.ipxe {% if env.IPXE_TLS_SETUP | lower == "true" %} -ipxe_config_template = /tmp/ipxe_config.template +ipxe_config_template = /templates/ipxe_config.template {% endif %} [redfish] use_swift = false -kernel_append_params = nofb nomodeset vga=normal ipa-insecure={{ env.IPA_INSECURE }} {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console=yes net.ifnames={{ '0' if env.PREDICTABLE_NIC_NAMES == 'false' else '1' }} +kernel_append_params = nofb nomodeset vga=normal ipa-insecure=1 {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console=yes [ilo] -kernel_append_params = nofb nomodeset vga=normal ipa-insecure={{ env.IPA_INSECURE }} {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console=yes net.ifnames={{ '0' if env.PREDICTABLE_NIC_NAMES == 'false' else '1' }} +kernel_append_params = nofb nomodeset vga=normal ipa-insecure=1 {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console=yes use_web_server_for_images = true [irmc] -kernel_append_params = nofb nomodeset vga=normal ipa-insecure={{ env.IPA_INSECURE }} {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console=yes net.ifnames={{ '0' if env.PREDICTABLE_NIC_NAMES == 'false' else '1' }} +kernel_append_params = nofb nomodeset vga=normal ipa-insecure=1 {% if env.ENABLE_FIPS_IPA %}fips={{ env.ENABLE_FIPS_IPA|trim }}{% endif %} {% if env.IRONIC_RAMDISK_SSH_KEY %}sshkey="{{ env.IRONIC_RAMDISK_SSH_KEY|trim }}"{% endif %} {{ env.IRONIC_KERNEL_PARAMS|trim }} systemd.journald.forward_to_console=yes [service_catalog] endpoint_override = {{ env.IRONIC_BASE_URL }} diff --git a/ironic-image/network-data-schema-empty.json b/ironic-image/ironic-config/network-data-schema-empty.json similarity index 100% rename from ironic-image/network-data-schema-empty.json rename to ironic-image/ironic-config/network-data-schema-empty.json diff --git a/ironic-image/ironic-probe.j2 b/ironic-image/ironic-probe.j2 deleted file mode 100644 index 85a5ca7..0000000 --- a/ironic-image/ironic-probe.j2 +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -eu -o pipefail - -curl -sSf {{ env.PROBE_CURL_ARGS }} "{{ env.PROBE_URL }}" - -# TODO(dtantsur): when PROBE_KIND==readiness, try the conductor and driver API -# to make sure the conductor is ready. This requires having access to secrets -# since these endpoints are authenticated. diff --git a/ironic-image/runironic b/ironic-image/runironic deleted file mode 100644 index d782fe1..0000000 --- a/ironic-image/runironic +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/bash - -# This setting must go before configure-ironic since it has different defaults. -export IRONIC_USE_MARIADB=${IRONIC_USE_MARIADB:-false} - -# shellcheck disable=SC1091 -. /bin/configure-ironic.sh - -# Ramdisk logs -mkdir -p /shared/log/ironic/deploy - -run_ironic_dbsync - -if [[ "$IRONIC_TLS_SETUP" == "true" ]] && [[ "${RESTART_CONTAINER_CERTIFICATE_UPDATED}" == "true" ]]; then - # shellcheck disable=SC2034 - python3.11 -m pyinotify --raw-format -e IN_DELETE_SELF -v "${IRONIC_CERT_FILE}" | while read -r file event; do - kill $(pgrep ironic) - done & -fi - -configure_ironic_auth - -exec /usr/bin/ironic diff --git a/ironic-image/runlogwatch.sh b/ironic-image/runlogwatch.sh deleted file mode 100644 index a2255d2..0000000 --- a/ironic-image/runlogwatch.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/bash - -# Ramdisk logs path -LOG_DIR="/shared/log/ironic/deploy" - -# The ironic container creates the directory, wait for -# it to exist before running inotifywait or it can fail causing -# a spurious restart -while [ ! -d "${LOG_DIR}" ]; do - echo "Waiting for ${LOG_DIR}" - sleep 5 -done - -python3.11 -m pyinotify --raw-format -e IN_CLOSE_WRITE -v "${LOG_DIR}" | - while read -r path _action file; do - echo "************ Contents of ${path}/${file} ramdisk log file bundle **************" - tar -xOzvvf "${path}/${file}" | sed -e "s/^/${file}: /" - rm -f "${path}/${file}" - done diff --git a/ironic-image/scripts/auth-common.sh b/ironic-image/scripts/auth-common.sh new file mode 100644 index 0000000..b9526d4 --- /dev/null +++ b/ironic-image/scripts/auth-common.sh @@ -0,0 +1,97 @@ +#!/usr/bin/bash + +set -euxo pipefail + +export IRONIC_REVERSE_PROXY_SETUP=${IRONIC_REVERSE_PROXY_SETUP:-false} + +# CUSTOM_CONFIG_DIR is also managed in the ironic-common.sh, in order to +# keep auth-common and ironic-common separate (to stay consistent with the +# architecture) part of the ironic-common logic had to be duplicated +CUSTOM_CONFIG_DIR="${CUSTOM_CONFIG_DIR:-/conf}" +IRONIC_CONF_DIR="${CUSTOM_CONFIG_DIR}/ironic" + +# Backward compatibility +if [[ "${IRONIC_DEPLOYMENT:-}" == "Conductor" ]]; then + export IRONIC_EXPOSE_JSON_RPC=true +else + export IRONIC_EXPOSE_JSON_RPC="${IRONIC_EXPOSE_JSON_RPC:-false}" +fi + +IRONIC_HTPASSWD_FILE="${IRONIC_CONF_DIR}/htpasswd" +export IRONIC_RPC_HTPASSWD_FILE="${IRONIC_HTPASSWD_FILE}-rpc" +if [[ -f "/auth/ironic/htpasswd" ]]; then + IRONIC_HTPASSWD=$( "${IRONIC_RPC_HTPASSWD_FILE}" + fi +} + +configure_ironic_auth() +{ + # Configure HTTP basic auth for API server + if [[ -n "${IRONIC_HTPASSWD}" ]]; then + printf "%s\n" "${IRONIC_HTPASSWD}" > "${IRONIC_HTPASSWD_FILE}" + if [[ "${IRONIC_REVERSE_PROXY_SETUP}" == "false" ]]; then + crudini --set "${IRONIC_CONFIG}" DEFAULT auth_strategy http_basic + crudini --set "${IRONIC_CONFIG}" DEFAULT http_basic_auth_user_file "${IRONIC_HTPASSWD_FILE}" + fi + fi +} + +write_htpasswd_files() +{ + if [[ -n "${IRONIC_HTPASSWD:-}" ]]; then + printf "%s\n" "${IRONIC_HTPASSWD}" > "${IRONIC_HTPASSWD_FILE}" + fi +} diff --git a/ironic-image/configure-ironic.sh b/ironic-image/scripts/configure-ironic.sh similarity index 71% rename from ironic-image/configure-ironic.sh rename to ironic-image/scripts/configure-ironic.sh index 8ab2404..f7c3c21 100644 --- a/ironic-image/configure-ironic.sh +++ b/ironic-image/scripts/configure-ironic.sh @@ -19,10 +19,11 @@ export IRONIC_ENABLE_VLAN_INTERFACES=${IRONIC_ENABLE_VLAN_INTERFACES:-${IRONIC_I export HTTP_PORT=${HTTP_PORT:-80} -export IRONIC_USE_MARIADB=${IRONIC_USE_MARIADB:-true} - -if [[ "$IRONIC_USE_MARIADB" == "true" ]]; then - MARIADB_PASSWORD=${MARIADB_PASSWORD} +if [[ "${IRONIC_USE_MARIADB}" == true ]]; then + if [[ -z "${MARIADB_PASSWORD:-}" ]]; then + echo "FATAL: IRONIC_USE_MARIADB requires password, mount a secret under /auth/mariadb" + exit 1 + fi MARIADB_DATABASE=${MARIADB_DATABASE:-ironic} MARIADB_USER=${MARIADB_USER:-ironic} MARIADB_HOST=${MARIADB_HOST:-127.0.0.1} @@ -32,13 +33,9 @@ if [[ "$IRONIC_USE_MARIADB" == "true" ]]; then fi fi -# TODO(dtantsur): remove the explicit default once we get -# https://review.opendev.org/761185 in the repositories -NUMPROC="$(grep -c "^processor" /proc/cpuinfo)" -if [[ "$NUMPROC" -lt 4 ]]; then - NUMPROC=4 -fi -export NUMWORKERS=${NUMWORKERS:-$NUMPROC} +# zero makes it do cpu number detection on Ironic side +export NUMWORKERS=${NUMWORKERS:-0} + # Whether to enable fast_track provisioning or not export IRONIC_FAST_TRACK=${IRONIC_FAST_TRACK:-true} @@ -57,8 +54,6 @@ wait_for_interface_or_ip # Hostname to use for the current conductor instance. export IRONIC_CONDUCTOR_HOST=${IRONIC_CONDUCTOR_HOST:-${IRONIC_URL_HOST}} -export IRONIC_BASE_URL=${IRONIC_BASE_URL:-"${IRONIC_SCHEME}://${IRONIC_URL_HOST}:${IRONIC_ACCESS_PORT}"} - if [[ -n "$IRONIC_EXTERNAL_IP" ]]; then export IRONIC_EXTERNAL_CALLBACK_URL=${IRONIC_EXTERNAL_CALLBACK_URL:-"${IRONIC_SCHEME}://${IRONIC_EXTERNAL_IP}:${IRONIC_ACCESS_PORT}"} if [[ "$IRONIC_VMEDIA_TLS_SETUP" == "true" ]]; then @@ -74,9 +69,9 @@ if [[ -f "${IMAGE_CACHE_PREFIX}.kernel" ]] && [[ -f "${IMAGE_CACHE_PREFIX}.initr export IRONIC_DEFAULT_RAMDISK="${IMAGE_CACHE_PREFIX}.initramfs" fi -if [[ -f /etc/ironic/ironic.conf ]]; then +if [[ -f "${IRONIC_CONF_DIR}/ironic.conf" ]]; then # Make a copy of the original supposed empty configuration file - cp /etc/ironic/ironic.conf /etc/ironic/ironic.conf_orig + cp "${IRONIC_CONF_DIR}/ironic.conf" "${IRONIC_CONF_DIR}/ironic.conf.orig" fi # oslo.config also supports Config Opts From Environment, log them to stdout @@ -86,34 +81,16 @@ env | grep "^OS_" || true mkdir -p /shared/html mkdir -p /shared/ironic_prometheus_exporter -configure_json_rpc_auth - if [[ -f /proc/sys/crypto/fips_enabled ]]; then ENABLE_FIPS_IPA=$(cat /proc/sys/crypto/fips_enabled) export ENABLE_FIPS_IPA fi # The original ironic.conf is empty, and can be found in ironic.conf_orig -render_j2_config /etc/ironic/ironic.conf.j2 /etc/ironic/ironic.conf +render_j2_config "/etc/ironic/ironic.conf.j2" \ + "${IRONIC_CONF_DIR}/ironic.conf" -configure_client_basic_auth ironic-rpc +configure_json_rpc_auth # Make sure ironic traffic bypasses any proxies export NO_PROXY="${NO_PROXY:-},$IRONIC_IP" - -PROBE_CURL_ARGS= -if [[ "${IRONIC_REVERSE_PROXY_SETUP}" == "true" ]]; then - if [[ "${IRONIC_PRIVATE_PORT}" == "unix" ]]; then - PROBE_URL="http://127.0.0.1:6385" - PROBE_CURL_ARGS="--unix-socket /shared/ironic.sock" - else - PROBE_URL="http://127.0.0.1:${IRONIC_PRIVATE_PORT}" - fi -else - PROBE_URL="${IRONIC_BASE_URL}" -fi -export PROBE_CURL_ARGS -export PROBE_URL - -PROBE_KIND=readiness render_j2_config /bin/ironic-probe.j2 /bin/ironic-readiness -PROBE_KIND=liveness render_j2_config /bin/ironic-probe.j2 /bin/ironic-liveness diff --git a/ironic-image/ironic-common.sh b/ironic-image/scripts/ironic-common.sh similarity index 51% rename from ironic-image/ironic-common.sh rename to ironic-image/scripts/ironic-common.sh index fd740f3..b47ff38 100644 --- a/ironic-image/ironic-common.sh +++ b/ironic-image/scripts/ironic-common.sh @@ -2,11 +2,36 @@ set -euxo pipefail -IRONIC_IP="${IRONIC_IP:-}" +# Export IRONIC_IP to avoid needing to lean on IRONIC_URL_HOST for consumption in +# e.g. dnsmasq configuration +export IRONIC_IP="${IRONIC_IP:-}" PROVISIONING_INTERFACE="${PROVISIONING_INTERFACE:-}" PROVISIONING_IP="${PROVISIONING_IP:-}" PROVISIONING_MACS="${PROVISIONING_MACS:-}" 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_provisioning_interface() { @@ -19,13 +44,13 @@ get_provisioning_interface() local interface="provisioning" if [[ -n "${PROVISIONING_IP}" ]]; then - if ip -br addr show | grep -qi " ${PROVISIONING_IP}/"; then + if ip -br addr show | grep -i " ${PROVISIONING_IP}/" &>/dev/null; then interface="$(ip -br addr show | grep -i " ${PROVISIONING_IP}/" | cut -f 1 -d ' ' | cut -f 1 -d '@')" fi fi for mac in ${PROVISIONING_MACS//,/ }; do - if ip -br link show up | grep -qi "$mac"; then + 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 @@ -42,9 +67,12 @@ 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 - # Convert the address using ipcalc which strips out the subnet. For IPv6 addresses, this will give the short-form address + # 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 + # Convert the address using ipcalc which strips out the subnet. + # For IPv6 addresses, this will give the short-form address IRONIC_IP="$(ipcalc "${PROVISIONING_IP}" | grep "^Address:" | awk '{print $2}')" export IRONIC_IP until grep -F " ${IRONIC_IP}/" <(ip -br addr show); do @@ -69,31 +97,37 @@ wait_for_interface_or_ip() export IPV=4 export IRONIC_URL_HOST="$IRONIC_IP" 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() { - ls $1 # DEBUG - python3 -c 'import os; import sys; import jinja2; sys.stdout.write(jinja2.Template(sys.stdin.read()).render(env=os.environ))' < "$1" - python3 -c 'import os; import sys; import jinja2; sys.stdout.write(jinja2.Template(sys.stdin.read()).render(env=os.environ))' < "$1" > "$2" - ls $2 # DEBUG + 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}" == "true" ]]; then + 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 /etc/ironic/ironic.conf upgrade; do + 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 - DB_VERSION="$(ironic-dbsync --config-file /etc/ironic/ironic.conf version)" + # 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 /etc/ironic/ironic.conf create_schema + ironic-dbsync --config-file "${IRONIC_CONF_DIR}/ironic.conf" create_schema fi fi } diff --git a/ironic-image/scripts/ironic-probe.sh b/ironic-image/scripts/ironic-probe.sh new file mode 100755 index 0000000..defe5c8 --- /dev/null +++ b/ironic-image/scripts/ironic-probe.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -eu -o pipefail + +# shellcheck disable=SC1091 +. /bin/ironic-common.sh +# shellcheck disable=SC1091 +. /bin/auth-common.sh + +PROBE_CURL_ARGS= +if [[ "${IRONIC_REVERSE_PROXY_SETUP}" == "true" ]]; then + if [[ "${IRONIC_PRIVATE_PORT}" == "unix" ]]; then + PROBE_URL="http://127.0.0.1:6385" + PROBE_CURL_ARGS="--unix-socket /shared/ironic.sock" + else + PROBE_URL="http://127.0.0.1:${IRONIC_PRIVATE_PORT}" + fi +else + PROBE_URL="${IRONIC_BASE_URL}" +fi + +# shellcheck disable=SC2086 +curl -sSf ${PROBE_CURL_ARGS} "${PROBE_URL}" diff --git a/ironic-image/scripts/rundatabase-upgrade b/ironic-image/scripts/rundatabase-upgrade new file mode 100755 index 0000000..26fb1f4 --- /dev/null +++ b/ironic-image/scripts/rundatabase-upgrade @@ -0,0 +1,10 @@ +#!/usr/bin/bash + +set -euxo pipefail + +# shellcheck disable=SC1091 +. /bin/configure-ironic.sh + +# NOTE(dtantsur): no retries here: this script is supposed to be run as a Job +# that is retried on failure. +exec ironic-dbsync --config-file "${IRONIC_CONF_DIR}/ironic.conf" upgrade diff --git a/ironic-image/rundnsmasq b/ironic-image/scripts/rundnsmasq similarity index 59% rename from ironic-image/rundnsmasq rename to ironic-image/scripts/rundnsmasq index 16f4c76..5368ef6 100644 --- a/ironic-image/rundnsmasq +++ b/ironic-image/scripts/rundnsmasq @@ -13,7 +13,11 @@ export DNS_PORT=${DNS_PORT:-0} wait_for_interface_or_ip if [[ "${DNS_IP:-}" == "provisioning" ]]; then - export DNS_IP="$IRONIC_URL_HOST" + if [[ "${IPV}" == "4" ]]; then + export DNS_IP="${IRONIC_IP}" + else + export DNS_IP="[${IRONIC_IP}]" + fi fi mkdir -p /shared/tftpboot @@ -32,12 +36,12 @@ fi # Template and write dnsmasq.conf # we template via /tmp as sed otherwise creates temp files in /etc directory # where we can't write -python3 -c 'import os; import sys; import jinja2; sys.stdout.write(jinja2.Template(sys.stdin.read()).render(env=os.environ))' /tmp/dnsmasq.conf +python3.11 -c 'import os; import sys; import jinja2; sys.stdout.write(jinja2.Template(sys.stdin.read()).render(env=os.environ))' <"/templates/dnsmasq.conf.j2" >"${DNSMASQ_TEMP_DIR}/dnsmasq_temp.conf" for iface in $(echo "$DNSMASQ_EXCEPT_INTERFACE" | tr ',' ' '); do - sed -i -e "/^interface=.*/ a\except-interface=${iface}" /tmp/dnsmasq.conf + sed -i -e "/^interface=.*/ a\except-interface=${iface}" "${DNSMASQ_TEMP_DIR}/dnsmasq_temp.conf" done -cat /tmp/dnsmasq.conf > /etc/dnsmasq.conf -rm /tmp/dnsmasq.conf +cat "${DNSMASQ_TEMP_DIR}/dnsmasq_temp.conf" > "${DNSMASQ_CONF_DIR}/dnsmasq.conf" +rm "${DNSMASQ_TEMP_DIR}/dnsmasq_temp.conf" -exec /usr/sbin/dnsmasq -d -q -C /etc/dnsmasq.conf +exec /usr/sbin/dnsmasq -d -q -C "${DNSMASQ_CONF_DIR}/dnsmasq.conf" diff --git a/ironic-image/runhttpd b/ironic-image/scripts/runhttpd similarity index 60% rename from ironic-image/runhttpd rename to ironic-image/scripts/runhttpd index d6175ba..bbd973f 100644 --- a/ironic-image/runhttpd +++ b/ironic-image/scripts/runhttpd @@ -28,25 +28,28 @@ wait_for_interface_or_ip mkdir -p /shared/html chmod 0777 /shared/html -IRONIC_BASE_URL="${IRONIC_SCHEME}://${IRONIC_URL_HOST}" - -INSPECTOR_EXTRA_ARGS=" ipa-inspection-callback-url=${IRONIC_BASE_URL}:${IRONIC_ACCESS_PORT}/v1/continue_inspection" +INSPECTOR_EXTRA_ARGS=" ipa-inspection-callback-url=${IRONIC_BASE_URL}/v1/continue_inspection" if [[ "$IRONIC_FAST_TRACK" == "true" ]]; then - INSPECTOR_EXTRA_ARGS+=" ipa-api-url=${IRONIC_BASE_URL}:${IRONIC_ACCESS_PORT}" + INSPECTOR_EXTRA_ARGS+=" ipa-api-url=${IRONIC_BASE_URL}" fi export INSPECTOR_EXTRA_ARGS # Copy files to shared mount -render_j2_config /tmp/inspector.ipxe.j2 /shared/html/inspector.ipxe -cp /tmp/uefi_esp*.img /shared/html/ +render_j2_config /templates/inspector.ipxe.j2 /shared/html/inspector.ipxe +# cp -r /etc/httpd/* "${HTTPD_DIR}" +if [[ -f "${HTTPD_CONF_DIR}/httpd.conf" ]]; then + mv "${HTTPD_CONF_DIR}/httpd.conf" "${HTTPD_CONF_DIR}/httpd.conf.example" +fi # Render the core httpd config -render_j2_config /etc/httpd/conf/httpd.conf.j2 /etc/httpd/conf/httpd.conf +render_j2_config "/etc/httpd/conf/httpd.conf.j2" \ + "${HTTPD_CONF_DIR}/httpd.conf" if [[ "$IRONIC_TLS_SETUP" == "true" ]]; then if [[ "${IRONIC_REVERSE_PROXY_SETUP}" == "true" ]]; then - render_j2_config /tmp/httpd-ironic-api.conf.j2 /etc/httpd/conf.d/ironic.conf + render_j2_config "/templates/httpd-ironic-api.conf.j2" \ + "${HTTPD_CONF_DIR_D}/ironic.conf" fi else export IRONIC_REVERSE_PROXY_SETUP="false" # If TLS is not used, we have no reason to use the reverse proxy @@ -56,33 +59,24 @@ write_htpasswd_files # Render httpd TLS configuration for /shared/html/ if [[ "$IRONIC_VMEDIA_TLS_SETUP" == "true" ]]; then - render_j2_config /etc/httpd-vmedia.conf.j2 /etc/httpd/conf.d/vmedia.conf + render_j2_config "/templates/httpd-vmedia.conf.j2" \ + "${HTTPD_CONF_DIR_D}/vmedia.conf" fi # Render httpd TLS configuration for /shared/html if [[ "$IPXE_TLS_SETUP" == "true" ]]; then mkdir -p /shared/html/custom-ipxe chmod 0777 /shared/html/custom-ipxe - render_j2_config "/etc/httpd-ipxe.conf.j2" "/etc/httpd/conf.d/ipxe.conf" + render_j2_config "/templates/httpd-ipxe.conf.j2" "${HTTPD_CONF_DIR_D}/ipxe.conf" cp "${IPXE_CUSTOM_FIRMWARE_DIR}/undionly.kpxe" \ "${IPXE_CUSTOM_FIRMWARE_DIR}/snponly.efi" \ "/shared/html/custom-ipxe" fi # Set up inotify to kill the container (restart) whenever cert files for ironic api change -if [[ "$IRONIC_TLS_SETUP" == "true" ]] && [[ "${RESTART_CONTAINER_CERTIFICATE_UPDATED}" == "true" ]]; then - # shellcheck disable=SC2034 - python3.11 -m pyinotify --raw-format -e IN_DELETE_SELF -v "${IRONIC_CERT_FILE}" | while read -r file event; do - kill -WINCH $(pgrep httpd) - done & -fi +configure_restart_on_certificate_update "${IRONIC_TLS_SETUP}" httpd "${IRONIC_CERT_FILE}" # Set up inotify to kill the container (restart) whenever cert of httpd for /shared/html/ path change -if [[ "$IRONIC_VMEDIA_TLS_SETUP" == "true" ]] && [[ "${RESTART_CONTAINER_CERTIFICATE_UPDATED}" == "true" ]]; then - # shellcheck disable=SC2034 - python3.11 -m pyinotify --raw-format -e IN_DELETE_SELF -v "${IRONIC_VMEDIA_CERT_FILE}" | while read -r file event; do - kill -WINCH $(pgrep httpd) - done & -fi +configure_restart_on_certificate_update "${IRONIC_VMEDIA_TLS_SETUP}" httpd "${IRONIC_VMEDIA_CERT_FILE}" -exec /usr/sbin/httpd -DFOREGROUND -f /etc/httpd/conf/httpd.conf +exec /usr/sbin/httpd -DFOREGROUND -f "${HTTPD_CONF_DIR}/httpd.conf" diff --git a/ironic-image/scripts/runironic b/ironic-image/scripts/runironic new file mode 100644 index 0000000..2d2b35e --- /dev/null +++ b/ironic-image/scripts/runironic @@ -0,0 +1,18 @@ +#!/usr/bin/bash + +# shellcheck disable=SC1091 +. /bin/configure-ironic.sh + +# Ramdisk logs +mkdir -p /shared/log/ironic/deploy + +# Allows skipping dbsync if it's done by an external job +if [[ "${IRONIC_SKIP_DBSYNC:-false}" != true ]]; then + run_ironic_dbsync +fi + +configure_restart_on_certificate_update "${IRONIC_TLS_SETUP}" ironic "${IRONIC_CERT_FILE}" + +configure_ironic_auth + +exec /usr/bin/ironic --config-dir "${IRONIC_CONF_DIR}" diff --git a/ironic-image/runironic-exporter b/ironic-image/scripts/runironic-exporter similarity index 71% rename from ironic-image/runironic-exporter rename to ironic-image/scripts/runironic-exporter index f2f60f2..e429a45 100644 --- a/ironic-image/runironic-exporter +++ b/ironic-image/scripts/runironic-exporter @@ -2,11 +2,13 @@ # shellcheck disable=SC1091 . /bin/configure-ironic.sh +# shellcheck disable=SC1091 +. /bin/ironic-common.sh FLASK_RUN_HOST=${FLASK_RUN_HOST:-0.0.0.0} FLASK_RUN_PORT=${FLASK_RUN_PORT:-9608} -export IRONIC_CONFIG="/etc/ironic/ironic.conf" +export IRONIC_CONFIG="${IRONIC_CONF_DIR}/ironic.conf" exec gunicorn -b "${FLASK_RUN_HOST}:${FLASK_RUN_PORT}" -w 4 \ ironic_prometheus_exporter.app.wsgi:application diff --git a/ironic-image/scripts/runlogwatch.sh b/ironic-image/scripts/runlogwatch.sh new file mode 100644 index 0000000..1de5658 --- /dev/null +++ b/ironic-image/scripts/runlogwatch.sh @@ -0,0 +1,21 @@ +#!/usr/bin/bash + +# Ramdisk logs path +LOG_DIR="/shared/log/ironic/deploy" + +mkdir -p "${LOG_DIR}" + +# shellcheck disable=SC2034 +python3.11 -m pyinotify --raw-format -e IN_CLOSE_WRITE -v "${LOG_DIR}" | + while read -r event dir mask maskname filename filepath pathname wd; do + #NOTE(elfosardo): a pyinotify event looks like this: + # + FILENAME=$(echo "${filename}" | cut -d'=' -f2-) + echo "************ Contents of ${LOG_DIR}/${FILENAME} ramdisk log file bundle **************" + tar -tzf "${LOG_DIR}/${FILENAME}" | while read -r entry; do + echo "${FILENAME}: **** Entry: ${entry} ****" + tar -xOzf "${LOG_DIR}/${FILENAME}" "${entry}" | sed -e "s/^/${FILENAME}: /" + echo + done + rm -f "${LOG_DIR}/${FILENAME}" + done diff --git a/ironic-image/scripts/runonline-data-migrations b/ironic-image/scripts/runonline-data-migrations new file mode 100755 index 0000000..14383c4 --- /dev/null +++ b/ironic-image/scripts/runonline-data-migrations @@ -0,0 +1,10 @@ +#!/usr/bin/bash + +set -euxo pipefail + +# shellcheck disable=SC1091 +. /bin/configure-ironic.sh + +# NOTE(dtantsur): no retries here: this script is supposed to be run as a Job +# that is retried on failure. +exec ironic-dbsync --config-file "${IRONIC_CONF_DIR}/ironic.conf" online_data_migrations diff --git a/ironic-image/tls-common.sh b/ironic-image/scripts/tls-common.sh similarity index 82% rename from ironic-image/tls-common.sh rename to ironic-image/scripts/tls-common.sh index 6805885..622d61b 100644 --- a/ironic-image/tls-common.sh +++ b/ironic-image/scripts/tls-common.sh @@ -20,11 +20,6 @@ export MARIADB_CACERT_FILE=/certs/ca/mariadb/tls.crt export IPXE_TLS_PORT="${IPXE_TLS_PORT:-8084}" -mkdir -p /certs/ironic -mkdir -p /certs/ca/ironic -mkdir -p /certs/ipxe -mkdir -p /certs/vmedia - if [[ -f "$IRONIC_CERT_FILE" ]] && [[ ! -f "$IRONIC_KEY_FILE" ]]; then echo "Missing TLS Certificate key file $IRONIC_KEY_FILE" exit 1 @@ -69,6 +64,7 @@ if [[ -f "$IRONIC_CERT_FILE" ]] || [[ -f "$IRONIC_CACERT_FILE" ]]; then export IRONIC_TLS_SETUP="true" export IRONIC_SCHEME="https" if [[ ! -f "$IRONIC_CACERT_FILE" ]]; then + mkdir -p "$(dirname "${IRONIC_CACERT_FILE}")" copy_atomic "$IRONIC_CERT_FILE" "$IRONIC_CACERT_FILE" fi else @@ -95,3 +91,21 @@ if [[ -f "$MARIADB_CACERT_FILE" ]]; then else export MARIADB_TLS_ENABLED="false" fi + +configure_restart_on_certificate_update() +{ + local enabled="$1" + local service="$2" + local cert_file="$3" + local signal="TERM" + + if [[ "${enabled}" == "true" ]] && [[ "${RESTART_CONTAINER_CERTIFICATE_UPDATED}" == "true" ]]; then + if [[ "${service}" == httpd ]]; then + signal="WINCH" + fi + python3.12 -m pyinotify --raw-format -e IN_DELETE_SELF -v "${cert_file}" | + while read -r; do + pkill "-${signal}" "${service}" + done & + fi +}