diff --git a/cluster-api-controller-image/Dockerfile b/cluster-api-controller-image/Dockerfile new file mode 100644 index 0000000..f4057c5 --- /dev/null +++ b/cluster-api-controller-image/Dockerfile @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: Apache-2.0 +#!BuildTag: %%IMG_PREFIX%%cluster-api-controller:v%%cluster-api_version%% +#!BuildTag: %%IMG_PREFIX%%cluster-api-controller:%%cluster-api_version%% +#!BuildTag: %%IMG_PREFIX%%cluster-api-controller:%%cluster-api_version%%-%RELEASE% +#!BuildVersion: 15.6 +ARG SLE_VERSION +FROM registry.suse.com/bci/bci-micro:$SLE_VERSION AS micro + +FROM registry.suse.com/bci/bci-base:$SLE_VERSION AS base +COPY --from=micro / /installroot/ +RUN zypper --installroot /installroot --non-interactive install --no-recommends cluster-api-175 shadow; zypper -n clean; rm -rf /var/log/* + +FROM micro AS final +# Define labels according to https://en.opensuse.org/Building_derived_containers +# labelprefix=com.suse.application.cluster-api +LABEL org.opencontainers.image.authors="SUSE LLC (https://www.suse.com/)" +LABEL org.opencontainers.image.title="SLE cluster-api Container Image" +LABEL org.opencontainers.image.description="cluster-api based on the SLE Base Container Image." +LABEL org.opencontainers.image.version="%%cluster-api_version%%" +LABEL org.opencontainers.image.url="https://www.suse.com/products/server/" +LABEL org.opencontainers.image.created="%BUILDTIME%" +LABEL org.opencontainers.image.vendor="SUSE LLC" +LABEL org.opensuse.reference="%%IMG_REPO%%/%%IMG_PREFIX%%cluster-api:%%cluster-api_version%%-%RELEASE%" +LABEL org.openbuildservice.disturl="%DISTURL%" +LABEL com.suse.supportlevel="l3" +LABEL com.suse.eula="SUSE Combined EULA February 2024" +LABEL com.suse.lifecycle-url="https://www.suse.com/lifecycle" +LABEL com.suse.image-type="application" +LABEL com.suse.release-stage="released" +# endlabelprefix + +COPY --from=base /installroot / +RUN mv /usr/bin/cluster-api-controller /manager +# Use uid of nonroot user (65532) because kubernetes expects numeric user when applying pod security policies +USER 65532 +ENTRYPOINT [ "/manager" ] diff --git a/cluster-api-controller-image/_service b/cluster-api-controller-image/_service new file mode 100644 index 0000000..9f48c56 --- /dev/null +++ b/cluster-api-controller-image/_service @@ -0,0 +1,17 @@ + + + + + Dockerfile + %%cluster-api_version%% + cluster-api-175 + patch + + + Dockerfile + IMG_PREFIX=$(rpm --macros=/root/.rpmmacros -E %img_prefix) + IMG_PREFIX + IMG_REPO=$(rpm --macros=/root/.rpmmacros -E %img_repo) + IMG_REPO + + diff --git a/cluster-api-operator-image/Dockerfile b/cluster-api-operator-image/Dockerfile new file mode 100644 index 0000000..8244d9c --- /dev/null +++ b/cluster-api-operator-image/Dockerfile @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: Apache-2.0 +#!BuildTag: %%IMG_PREFIX%%cluster-api-operator:%%cluster-api-operator_version%% +#!BuildTag: %%IMG_PREFIX%%cluster-api-operator:%%cluster-api-operator_version%%-%RELEASE% +#!BuildVersion: 15.6 +ARG SLE_VERSION +FROM registry.suse.com/bci/bci-micro:$SLE_VERSION AS micro + +FROM registry.suse.com/bci/bci-base:$SLE_VERSION AS base +COPY --from=micro / /installroot/ +RUN zypper --installroot /installroot --non-interactive install --no-recommends cluster-api-operator-012 shadow; zypper -n clean; rm -rf /var/log/* + +FROM micro AS final +# Define labels according to https://en.opensuse.org/Building_derived_containers +# labelprefix=com.suse.application.cluster-api-operator +LABEL org.opencontainers.image.authors="SUSE LLC (https://www.suse.com/)" +LABEL org.opencontainers.image.title="SLE cluster-api-operator Container Image" +LABEL org.opencontainers.image.description="cluster-api-operator based on the SLE Base Container Image." +LABEL org.opencontainers.image.version="%%cluster-api-operator_version%%" +LABEL org.opencontainers.image.url="https://www.suse.com/products/server/" +LABEL org.opencontainers.image.created="%BUILDTIME%" +LABEL org.opencontainers.image.vendor="SUSE LLC" +LABEL org.opensuse.reference="%%IMG_REPO%%/%%IMG_PREFIX%%cluster-api-operator:%%cluster-api-operator_version%%-%RELEASE%" +LABEL org.openbuildservice.disturl="%DISTURL%" +LABEL com.suse.supportlevel="l3" +LABEL com.suse.eula="SUSE Combined EULA February 2024" +LABEL com.suse.lifecycle-url="https://www.suse.com/lifecycle" +LABEL com.suse.image-type="application" +LABEL com.suse.release-stage="released" +# endlabelprefix + +COPY --from=base /installroot / +RUN mv /usr/bin/cluster-api-operator-controller /manager +# Use uid of nonroot user (65532) because kubernetes expects numeric user when applying pod security policies +USER 65532 +ENTRYPOINT [ "/manager" ] diff --git a/cluster-api-operator-image/_service b/cluster-api-operator-image/_service new file mode 100644 index 0000000..646f2d0 --- /dev/null +++ b/cluster-api-operator-image/_service @@ -0,0 +1,17 @@ + + + + + Dockerfile + %%cluster-api-operator_version%% + cluster-api-operator-012 + patch + + + Dockerfile + IMG_PREFIX=$(rpm --macros=/root/.rpmmacros -E %img_prefix) + IMG_PREFIX + IMG_REPO=$(rpm --macros=/root/.rpmmacros -E %img_repo) + IMG_REPO + + diff --git a/cluster-api-provider-metal3-image/Dockerfile b/cluster-api-provider-metal3-image/Dockerfile new file mode 100644 index 0000000..edd6d38 --- /dev/null +++ b/cluster-api-provider-metal3-image/Dockerfile @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: Apache-2.0 +#!BuildTag: %%IMG_PREFIX%%cluster-api-provider-metal3:v%%cluster-api-provider-metal3_version%% +#!BuildTag: %%IMG_PREFIX%%cluster-api-provider-metal3:%%cluster-api-provider-metal3_version%% +#!BuildTag: %%IMG_PREFIX%%cluster-api-provider-metal3:%%cluster-api-provider-metal3_version%%-%RELEASE% +#!BuildVersion: 15.6 +ARG SLE_VERSION +FROM registry.suse.com/bci/bci-micro:$SLE_VERSION AS micro + +FROM registry.suse.com/bci/bci-base:$SLE_VERSION AS base +COPY --from=micro / /installroot/ +RUN zypper --installroot /installroot --non-interactive install --no-recommends cluster-api-provider-metal3-171 shadow; zypper -n clean; rm -rf /var/log/* + +FROM micro AS final +# Define labels according to https://en.opensuse.org/Building_derived_containers +# labelprefix=com.suse.application.cluster-api-provider-metal3 +LABEL org.opencontainers.image.authors="SUSE LLC (https://www.suse.com/)" +LABEL org.opencontainers.image.title="SLE cluster-api-provider-metal3 Container Image" +LABEL org.opencontainers.image.description="cluster-api-provider-metal3 based on the SLE Base Container Image." +LABEL org.opencontainers.image.version="%%cluster-api-provider-metal3_version%%" +LABEL org.opencontainers.image.url="https://www.suse.com/products/server/" +LABEL org.opencontainers.image.created="%BUILDTIME%" +LABEL org.opencontainers.image.vendor="SUSE LLC" +LABEL org.opensuse.reference="%%IMG_REPO%%/%%IMG_PREFIX%%cluster-api-provider-metal3:%%cluster-api-provider-metal3_version%%-%RELEASE%" +LABEL org.openbuildservice.disturl="%DISTURL%" +LABEL com.suse.supportlevel="l3" +LABEL com.suse.eula="SUSE Combined EULA February 2024" +LABEL com.suse.lifecycle-url="https://www.suse.com/lifecycle" +LABEL com.suse.image-type="application" +LABEL com.suse.release-stage="released" +# endlabelprefix + +COPY --from=base /installroot / +RUN mv /usr/bin/cluster-api-provider-metal3 /manager +# Use uid of nonroot user (65532) because kubernetes expects numeric user when applying pod security policies +USER 65532 +ENTRYPOINT [ "/manager" ] diff --git a/cluster-api-provider-metal3-image/_service b/cluster-api-provider-metal3-image/_service new file mode 100644 index 0000000..2ef5aa3 --- /dev/null +++ b/cluster-api-provider-metal3-image/_service @@ -0,0 +1,17 @@ + + + + + Dockerfile + %%cluster-api-provider-metal3_version%% + cluster-api-provider-metal3-171 + patch + + + Dockerfile + IMG_PREFIX=$(rpm --macros=/root/.rpmmacros -E %img_prefix) + IMG_PREFIX + IMG_REPO=$(rpm --macros=/root/.rpmmacros -E %img_repo) + IMG_REPO + + diff --git a/cluster-api-provider-rke2-bootstrap-image/Dockerfile b/cluster-api-provider-rke2-bootstrap-image/Dockerfile new file mode 100644 index 0000000..902d5db --- /dev/null +++ b/cluster-api-provider-rke2-bootstrap-image/Dockerfile @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: Apache-2.0 +#!BuildTag: %%IMG_PREFIX%%cluster-api-provider-rke2-bootstrap:v%%cluster-api-provider-rke2_version%% +#!BuildTag: %%IMG_PREFIX%%cluster-api-provider-rke2-bootstrap:%%cluster-api-provider-rke2_version%% +#!BuildTag: %%IMG_PREFIX%%cluster-api-provider-rke2-bootstrap:%%cluster-api-provider-rke2_version%%-%RELEASE% +#!BuildVersion: 15.6 +ARG SLE_VERSION +FROM registry.suse.com/bci/bci-micro:$SLE_VERSION AS micro + +FROM registry.suse.com/bci/bci-base:$SLE_VERSION AS base +COPY --from=micro / /installroot/ +RUN zypper --installroot /installroot --non-interactive install --no-recommends cluster-api-provider-rke2-070-bootstrap shadow; zypper -n clean; rm -rf /var/log/* + +FROM micro AS final +# Define labels according to https://en.opensuse.org/Building_derived_containers +# labelprefix=com.suse.application.cluster-api-provider-rke2 +LABEL org.opencontainers.image.authors="SUSE LLC (https://www.suse.com/)" +LABEL org.opencontainers.image.title="SLE cluster-api-provider-rke2 Container Image" +LABEL org.opencontainers.image.description="cluster-api-provider-rke2 based on the SLE Base Container Image." +LABEL org.opencontainers.image.version="%%cluster-api-provider-rke2_version%%" +LABEL org.opencontainers.image.url="https://www.suse.com/products/server/" +LABEL org.opencontainers.image.created="%BUILDTIME%" +LABEL org.opencontainers.image.vendor="SUSE LLC" +LABEL org.opensuse.reference="%%IMG_REPO%%/%%IMG_PREFIX%%cluster-api-provider-rke2-bootstrap:%%cluster-api-provider-rke2_version%%-%RELEASE%" +LABEL org.openbuildservice.disturl="%DISTURL%" +LABEL com.suse.supportlevel="l3" +LABEL com.suse.eula="SUSE Combined EULA February 2024" +LABEL com.suse.lifecycle-url="https://www.suse.com/lifecycle" +LABEL com.suse.image-type="application" +LABEL com.suse.release-stage="released" +# endlabelprefix + +COPY --from=base /installroot / +RUN mv /usr/bin/rke2-bootstrap-manager /manager +# Use uid of nonroot user (65532) because kubernetes expects numeric user when applying pod security policies +USER 65532 +ENTRYPOINT [ "/manager" ] diff --git a/cluster-api-provider-rke2-bootstrap-image/_service b/cluster-api-provider-rke2-bootstrap-image/_service new file mode 100644 index 0000000..fa241da --- /dev/null +++ b/cluster-api-provider-rke2-bootstrap-image/_service @@ -0,0 +1,17 @@ + + + + + Dockerfile + %%cluster-api-provider-rke2_version%% + cluster-api-provider-rke2-070-bootstrap + patch + + + Dockerfile + IMG_PREFIX=$(rpm --macros=/root/.rpmmacros -E %img_prefix) + IMG_PREFIX + IMG_REPO=$(rpm --macros=/root/.rpmmacros -E %img_repo) + IMG_REPO + + diff --git a/cluster-api-provider-rke2-controlplane-image/Dockerfile b/cluster-api-provider-rke2-controlplane-image/Dockerfile new file mode 100644 index 0000000..db8ac94 --- /dev/null +++ b/cluster-api-provider-rke2-controlplane-image/Dockerfile @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: Apache-2.0 +#!BuildTag: %%IMG_PREFIX%%cluster-api-provider-rke2-controlplane:v%%cluster-api-provider-rke2_version%% +#!BuildTag: %%IMG_PREFIX%%cluster-api-provider-rke2-controlplane:%%cluster-api-provider-rke2_version%% +#!BuildTag: %%IMG_PREFIX%%cluster-api-provider-rke2-controlplane:%%cluster-api-provider-rke2_version%%-%RELEASE% +#!BuildVersion: 15.6 +ARG SLE_VERSION +FROM registry.suse.com/bci/bci-micro:$SLE_VERSION AS micro + +FROM registry.suse.com/bci/bci-base:$SLE_VERSION AS base +COPY --from=micro / /installroot/ +RUN zypper --installroot /installroot --non-interactive install --no-recommends cluster-api-provider-rke2-070-control-plane shadow; zypper -n clean; rm -rf /var/log/* + +FROM micro AS final +# Define labels according to https://en.opensuse.org/Building_derived_containers +# labelprefix=com.suse.application.cluster-api-provider-rke2 +LABEL org.opencontainers.image.authors="SUSE LLC (https://www.suse.com/)" +LABEL org.opencontainers.image.title="SLE cluster-api-provider-rke2 Container Image" +LABEL org.opencontainers.image.description="cluster-api-provider-rke2 based on the SLE Base Container Image." +LABEL org.opencontainers.image.version="%%cluster-api-provider-rke2_version%%" +LABEL org.opencontainers.image.url="https://www.suse.com/products/server/" +LABEL org.opencontainers.image.created="%BUILDTIME%" +LABEL org.opencontainers.image.vendor="SUSE LLC" +LABEL org.opensuse.reference="%%IMG_REPO%%/%%IMG_PREFIX%%cluster-api-provider-rke2-controlplane:%%cluster-api-provider-rke2_version%%-%RELEASE%" +LABEL org.openbuildservice.disturl="%DISTURL%" +LABEL com.suse.supportlevel="l3" +LABEL com.suse.eula="SUSE Combined EULA February 2024" +LABEL com.suse.lifecycle-url="https://www.suse.com/lifecycle" +LABEL com.suse.image-type="application" +LABEL com.suse.release-stage="released" +# endlabelprefix + +COPY --from=base /installroot / +RUN mv /usr/bin/rke2-control-plane-manager /manager +# Use uid of nonroot user (65532) because kubernetes expects numeric user when applying pod security policies +USER 65532 +ENTRYPOINT [ "/manager" ] diff --git a/cluster-api-provider-rke2-controlplane-image/_service b/cluster-api-provider-rke2-controlplane-image/_service new file mode 100644 index 0000000..7d398fe --- /dev/null +++ b/cluster-api-provider-rke2-controlplane-image/_service @@ -0,0 +1,17 @@ + + + + + Dockerfile + %%cluster-api-provider-rke2_version%% + cluster-api-provider-rke2-070-control-plane + patch + + + Dockerfile + IMG_PREFIX=$(rpm --macros=/root/.rpmmacros -E %img_prefix) + IMG_PREFIX + IMG_REPO=$(rpm --macros=/root/.rpmmacros -E %img_repo) + IMG_REPO + + diff --git a/edge-image-builder-image/Dockerfile b/edge-image-builder-image/Dockerfile new file mode 100644 index 0000000..714da54 --- /dev/null +++ b/edge-image-builder-image/Dockerfile @@ -0,0 +1,40 @@ +#!BuildTag: %%IMG_PREFIX%%edge-image-builder:1.1.0 +#!BuildTag: %%IMG_PREFIX%%edge-image-builder:1.1.0-%RELEASE% +#!BuildVersion: 15.6 +ARG SLE_VERSION +FROM registry.suse.com/bci/bci-base:$SLE_VERSION +MAINTAINER SUSE LLC (https://www.suse.com/) + +COPY artifacts.yaml artifacts.yaml + +RUN sed -i -e 's%^# rpm.install.excludedocs = no.*%rpm.install.excludedocs = yes%g' /etc/zypp/zypp.conf +RUN zypper --non-interactive install --no-recommends edge-image-builder-110 qemu-x86 qemu-uefi-aarch64 cni-plugins; zypper -n clean; rm -rf /var/log/* + +# Define labels according to https://en.opensuse.org/Building_derived_containers +# labelprefix=com.suse.application.edge-image-builder +LABEL org.opencontainers.image.authors="SUSE LLC (https://www.suse.com/)" +LABEL org.opencontainers.image.title="SLE edge-image-builder Container Image" +LABEL org.opencontainers.image.description="edge-image-builder based on the SLE Base Container Image." +LABEL org.opencontainers.image.version="1.1.0" +LABEL org.opencontainers.image.url="https://www.suse.com/products/server/" +LABEL org.opencontainers.image.created="%BUILDTIME%" +LABEL org.opencontainers.image.vendor="SUSE LLC" +LABEL org.opensuse.reference="%%IMG_REPO%%/%%IMG_PREFIX%%edge-image-builder:1.1.0-%RELEASE%" +LABEL org.openbuildservice.disturl="%DISTURL%" +LABEL com.suse.supportlevel="l3" +LABEL com.suse.eula="SUSE Combined EULA February 2024" +LABEL com.suse.lifecycle-url="https://www.suse.com/lifecycle" +LABEL com.suse.image-type="application" +LABEL com.suse.release-stage="released" +# endlabelprefix + +# Make adjustments for running guestfish and image modifications on aarch64 +# guestfish looks for very specific locations on the filesystem for UEFI firmware +# and also expects the boot kernel to be a portable executable (PE), not ELF. +RUN mkdir -p /usr/share/edk2/aarch64 && \ + cp /usr/share/qemu/aavmf-aarch64-code.bin /usr/share/edk2/aarch64/QEMU_EFI-pflash.raw && \ + cp /usr/share/qemu/aavmf-aarch64-vars.bin /usr/share/edk2/aarch64/vars-template-pflash.raw && \ + mv /boot/vmlinux* /boot/backup-vmlinux + +ENTRYPOINT ["/usr/bin/eib"] + diff --git a/edge-image-builder-image/_service b/edge-image-builder-image/_service new file mode 100644 index 0000000..3990297 --- /dev/null +++ b/edge-image-builder-image/_service @@ -0,0 +1,14 @@ + + + + Dockerfile + IMG_PREFIX=$(rpm --macros=/root/.rpmmacros -E %img_prefix) + IMG_PREFIX + IMG_REPO=$(rpm --macros=/root/.rpmmacros -E %img_repo) + IMG_REPO + artifacts.yaml + CHART_REPO=$(rpm --macros=/root/.rpmmacros -E %chart_repo) + CHART_REPO + + + diff --git a/edge-image-builder-image/artifacts.yaml b/edge-image-builder-image/artifacts.yaml new file mode 100644 index 0000000..c1c06f3 --- /dev/null +++ b/edge-image-builder-image/artifacts.yaml @@ -0,0 +1,16 @@ +metallb: + chart: metallb-chart + repository: %%CHART_REPO%%/3.1 + version: 0.14.9 +endpoint-copier-operator: + chart: endpoint-copier-operator-chart + repository: %%CHART_REPO%%/3.1 + version: 0.2.1 +kubernetes: + k3s: + selinuxPackage: k3s-selinux-1.6-1.slemicro.noarch + selinuxRepository: https://rpm.rancher.io/k3s/stable/common/slemicro/noarch + rke2: + selinuxPackage: rke2-selinux + selinuxRepository: https://rpm.rancher.io/rke2/stable/common/slemicro/noarch + diff --git a/ip-address-manager-image/Dockerfile b/ip-address-manager-image/Dockerfile new file mode 100644 index 0000000..672ad17 --- /dev/null +++ b/ip-address-manager-image/Dockerfile @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: Apache-2.0 +#!BuildTag: %%IMG_PREFIX%%ip-address-manager:v%%ip-address-manager_version%% +#!BuildTag: %%IMG_PREFIX%%ip-address-manager:%%ip-address-manager_version%% +#!BuildTag: %%IMG_PREFIX%%ip-address-manager:%%ip-address-manager_version%%-%RELEASE% +#!BuildVersion: 15.6 +ARG SLE_VERSION +FROM registry.suse.com/bci/bci-micro:$SLE_VERSION AS micro + +FROM registry.suse.com/bci/bci-base:$SLE_VERSION AS base +COPY --from=micro / /installroot/ +RUN zypper --installroot /installroot --non-interactive install --no-recommends ip-address-manager-171 shadow; zypper -n clean; rm -rf /var/log/* + +FROM micro AS final +# Define labels according to https://en.opensuse.org/Building_derived_containers +# labelprefix=com.suse.application.ip-address-manager +LABEL org.opencontainers.image.authors="SUSE LLC (https://www.suse.com/)" +LABEL org.opencontainers.image.title="SLE ip-address-manager Container Image" +LABEL org.opencontainers.image.description="ip-address-manager based on the SLE Base Container Image." +LABEL org.opencontainers.image.version="%%ip-address-manager_version%%" +LABEL org.opencontainers.image.url="https://www.suse.com/products/server/" +LABEL org.opencontainers.image.created="%BUILDTIME%" +LABEL org.opencontainers.image.vendor="SUSE LLC" +LABEL org.opensuse.reference="%%IMG_REPO%%/%%IMG_PREFIX%%ip-address-manager:%%ip-address-manager_version%%-%RELEASE%" +LABEL org.openbuildservice.disturl="%DISTURL%" +LABEL com.suse.supportlevel="l3" +LABEL com.suse.eula="SUSE Combined EULA February 2024" +LABEL com.suse.lifecycle-url="https://www.suse.com/lifecycle" +LABEL com.suse.image-type="application" +LABEL com.suse.release-stage="released" +# endlabelprefix + +COPY --from=base /installroot / +RUN mv /usr/bin/ip-address-manager /manager +# Use uid of nonroot user (65532) because kubernetes expects numeric user when applying pod security policies +USER 65532 +ENTRYPOINT [ "/manager" ] diff --git a/ip-address-manager-image/_service b/ip-address-manager-image/_service new file mode 100644 index 0000000..a0d1450 --- /dev/null +++ b/ip-address-manager-image/_service @@ -0,0 +1,17 @@ + + + + + Dockerfile + %%ip-address-manager_version%% + ip-address-manager-171 + patch + + + Dockerfile + IMG_PREFIX=$(rpm --macros=/root/.rpmmacros -E %img_prefix) + IMG_PREFIX + IMG_REPO=$(rpm --macros=/root/.rpmmacros -E %img_repo) + IMG_REPO + + diff --git a/ironic-image/Dockerfile b/ironic-image/Dockerfile new file mode 100644 index 0000000..830d31f --- /dev/null +++ b/ironic-image/Dockerfile @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: Apache-2.0 +#!BuildTag: %%IMG_PREFIX%%ironic:24.1.2.0 +#!BuildTag: %%IMG_PREFIX%%ironic:24.1.2.0-%RELEASE% +#!BuildVersion: 15.6 + +ARG SLE_VERSION +FROM registry.suse.com/bci/bci-micro:$SLE_VERSION AS micro + +FROM registry.suse.com/bci/bci-base:$SLE_VERSION AS base + +RUN set -euo pipefail; zypper -n in --no-recommends gcc git make xz-devel shim dosfstools mtools glibc-extra grub2-x86_64-efi grub2; zypper -n clean; rm -rf /var/log/* +WORKDIR /tmp +COPY prepare-efi.sh /bin/ +RUN set -euo pipefail; chmod +x /bin/prepare-efi.sh +RUN /bin/prepare-efi.sh + +COPY --from=micro / /installroot/ +RUN sed -i -e 's%^# rpm.install.excludedocs = no.*%rpm.install.excludedocs = yes%g' /etc/zypp/zypp.conf +RUN zypper --installroot /installroot --non-interactive install --no-recommends python311-devel python311 python311-pip python-dracclient python311-sushy-oem-idrac python311-proliantutils python311-sushy python3-ironicclient git curl sles-release tar gzip vim gawk dnsmasq dosfstools apache2 apache2-mod_wsgi inotify-tools ipcalc ipmitool iproute2 procps qemu-tools sqlite3 util-linux xorriso tftp syslinux ipxe-bootimgs python311-sushy-tools crudini openstack-ironic openstack-ironic-inspector-api + +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" +LABEL org.opencontainers.image.description="Openstack Ironic based on the SLE Base Container Image." +LABEL org.opencontainers.image.url="https://www.suse.com/products/server/" +LABEL org.opencontainers.image.created="%BUILDTIME%" +LABEL org.opencontainers.image.vendor="SUSE LLC" +LABEL org.opencontainers.image.version="24.1.2.0" +LABEL org.opensuse.reference="%%IMG_REPO%%/%%IMG_PREFIX%%ironic:24.1.2.0-%RELEASE%" +LABEL org.openbuildservice.disturl="%DISTURL%" +LABEL com.suse.supportlevel="l3" +LABEL com.suse.eula="SUSE Combined EULA February 2024" +LABEL com.suse.lifecycle-url="https://www.suse.com/lifecycle" +LABEL com.suse.image-type="application" +LABEL com.suse.release-stage="released" +# endlabelprefix + +COPY --from=base /installroot / + +RUN set -euo pipefail; ln -s /usr/bin/python3.11 /usr/local/bin/python3; \ + ln -s /usr/bin/pydoc3.11 /usr/local/bin/pydoc + +ENV GRUB_DIR=/tftpboot/boot/grub + +# workaround for mkisofs command failing +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 runironic-api runironic-conductor runironic-exporter runironic-inspector runlogwatch.sh tls-common.sh configure-nonroot.sh /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/runironic-api; chmod +x /bin/runironic-conductor; chmod +x /bin/runironic-exporter; chmod +x /bin/runironic-inspector; 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/ + +# IRONIC # +RUN cp /usr/share/ipxe/undionly.kpxe /tftpboot/undionly.kpxe +RUN cp /usr/share/ipxe/ipxe-x86_64.efi /tftpboot/ipxe.efi +COPY --from=base /tmp/esp.img /tmp/uefi_esp.img + +COPY ironic.conf.j2 /etc/ironic/ +COPY inspector.ipxe.j2 httpd-ironic-api.conf.j2 /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 + +# IRONIC-INSPECTOR # +RUN mkdir -p /var/lib/ironic /var/lib/ironic-inspector && \ + sqlite3 /var/lib/ironic/ironic.db "pragma journal_mode=wal" && \ + sqlite3 /var/lib/ironic-inspector/ironic-inspector.db "pragma journal_mode=wal" + +COPY ironic-inspector.conf.j2 /etc/ironic-inspector/ +COPY inspector-apache.conf.j2 /etc/httpd/conf.d/ + +# Workaround +# Removing the 010-ironic.conf file that comes with the package +RUN rm /etc/ironic/ironic.conf.d/010-ironic.conf + +# configure non-root user and set relevant permissions +RUN configure-nonroot.sh && \ + rm -f /bin/configure-nonroot.sh diff --git a/ironic-image/_service b/ironic-image/_service new file mode 100644 index 0000000..219c79f --- /dev/null +++ b/ironic-image/_service @@ -0,0 +1,17 @@ + + + + + Dockerfile + %%openstack-ironic_version%% + openstack-ironic + patch + + + Dockerfile + IMG_PREFIX=$(rpm --macros=/root/.rpmmacros -E %img_prefix) + IMG_PREFIX + IMG_REPO=$(rpm --macros=/root/.rpmmacros -E %img_repo) + IMG_REPO + + diff --git a/ironic-image/apache2-vmedia.conf.j2 b/ironic-image/apache2-vmedia.conf.j2 new file mode 100644 index 0000000..1d7ad21 --- /dev/null +++ b/ironic-image/apache2-vmedia.conf.j2 @@ -0,0 +1,27 @@ +Listen {{ env.VMEDIA_TLS_PORT }} + + + ErrorLog /dev/stderr + LogLevel debug + CustomLog /dev/stdout combined + + SSLEngine on + SSLProtocol {{ env.IRONIC_VMEDIA_SSL_PROTOCOL }} + SSLCertificateFile {{ env.IRONIC_VMEDIA_CERT_FILE }} + SSLCertificateKeyFile {{ env.IRONIC_VMEDIA_KEY_FILE }} + + + AllowOverride None + Require all granted + + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + + + + SSLRequireSSL + diff --git a/ironic-image/auth-common.sh b/ironic-image/auth-common.sh new file mode 100644 index 0000000..9906776 --- /dev/null +++ b/ironic-image/auth-common.sh @@ -0,0 +1,71 @@ +#!/usr/bin/bash + +set -euxo pipefail + +export IRONIC_HTPASSWD=${IRONIC_HTPASSWD:-${HTTP_BASIC_HTPASSWD:-}} +export INSPECTOR_HTPASSWD=${INSPECTOR_HTPASSWD:-${HTTP_BASIC_HTPASSWD:-}} +export IRONIC_DEPLOYMENT="${IRONIC_DEPLOYMENT:-}" +export IRONIC_REVERSE_PROXY_SETUP=${IRONIC_REVERSE_PROXY_SETUP:-false} +export INSPECTOR_REVERSE_PROXY_SETUP=${INSPECTOR_REVERSE_PROXY_SETUP:-false} + +IRONIC_HTPASSWD_FILE=/etc/ironic/htpasswd +INSPECTOR_HTPASSWD_FILE=/etc/ironic-inspector/htpasswd + +configure_client_basic_auth() +{ + local auth_config_file="/auth/$1/auth-config" + local dest="${2:-/etc/ironic/ironic.conf}" + if [[ -f "${auth_config_file}" ]]; then + # Merge configurations in the "auth" directory into the default ironic configuration file because there is no way to choose the configuration file + # when running the api as a WSGI app. + crudini --merge "${dest}" < "${auth_config_file}" + fi +} + +configure_json_rpc_auth() +{ + export JSON_RPC_AUTH_STRATEGY="noauth" + if [[ -n "${IRONIC_HTPASSWD}" ]]; then + if [[ "${IRONIC_DEPLOYMENT}" == "Conductor" ]]; then + export JSON_RPC_AUTH_STRATEGY="http_basic" + printf "%s\n" "${IRONIC_HTPASSWD}" > "${IRONIC_HTPASSWD_FILE}-rpc" + else + printf "%s\n" "${IRONIC_HTPASSWD}" > "${IRONIC_HTPASSWD_FILE}" + fi + 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 +} + +configure_inspector_auth() +{ + local config=/etc/ironic-inspector/ironic-inspector.conf + if [[ -n "${INSPECTOR_HTPASSWD}" ]]; then + printf "%s\n" "${INSPECTOR_HTPASSWD}" > "${INSPECTOR_HTPASSWD_FILE}" + if [[ "${INSPECTOR_REVERSE_PROXY_SETUP}" == "false" ]]; then + crudini --set "${config}" DEFAULT auth_strategy http_basic + crudini --set "${config}" DEFAULT http_basic_auth_user_file "${INSPECTOR_HTPASSWD_FILE}" + fi + fi +} + +write_htpasswd_files() +{ + if [[ -n "${IRONIC_HTPASSWD:-}" ]]; then + printf "%s\n" "${IRONIC_HTPASSWD}" > "${IRONIC_HTPASSWD_FILE}" + fi + if [[ -n "${INSPECTOR_HTPASSWD:-}" ]]; then + printf "%s\n" "${INSPECTOR_HTPASSWD}" > "${INSPECTOR_HTPASSWD_FILE}" + fi +} diff --git a/ironic-image/configure-ironic.sh b/ironic-image/configure-ironic.sh new file mode 100644 index 0000000..fa07f43 --- /dev/null +++ b/ironic-image/configure-ironic.sh @@ -0,0 +1,102 @@ +#!/usr/bin/bash + +set -euxo pipefail + +IRONIC_DEPLOYMENT="${IRONIC_DEPLOYMENT:-}" +IRONIC_EXTERNAL_IP="${IRONIC_EXTERNAL_IP:-}" + +# Define the VLAN interfaces to be included in introspection report, e.g. +# all - all VLANs on all interfaces using LLDP information +# - all VLANs on a particular interface using LLDP information +# - a particular VLAN on an interface, not relying on LLDP +export IRONIC_INSPECTOR_VLAN_INTERFACES=${IRONIC_INSPECTOR_VLAN_INTERFACES:-all} + +# shellcheck disable=SC1091 +. /bin/tls-common.sh +# shellcheck disable=SC1091 +. /bin/ironic-common.sh +# shellcheck disable=SC1091 +. /bin/auth-common.sh + +export HTTP_PORT=${HTTP_PORT:-80} + +MARIADB_PASSWORD=${MARIADB_PASSWORD} +MARIADB_DATABASE=${MARIADB_DATABASE:-ironic} +MARIADB_USER=${MARIADB_USER:-ironic} +MARIADB_HOST=${MARIADB_HOST:-127.0.0.1} +export MARIADB_CONNECTION="mysql+pymysql://${MARIADB_USER}:${MARIADB_PASSWORD}@${MARIADB_HOST}/${MARIADB_DATABASE}?charset=utf8" +if [[ "$MARIADB_TLS_ENABLED" == "true" ]]; then + export MARIADB_CONNECTION="${MARIADB_CONNECTION}&ssl=on&ssl_ca=${MARIADB_CACERT_FILE}" +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} + +export IRONIC_USE_MARIADB=${IRONIC_USE_MARIADB:-true} +export IRONIC_EXPOSE_JSON_RPC=${IRONIC_EXPOSE_JSON_RPC:-true} + +# Whether to enable fast_track provisioning or not +export IRONIC_FAST_TRACK=${IRONIC_FAST_TRACK:-true} + +# Whether cleaning disks before and after deployment +export IRONIC_AUTOMATED_CLEAN=${IRONIC_AUTOMATED_CLEAN:-true} + +# Wheter to enable the sensor data collection +export SEND_SENSOR_DATA=${SEND_SENSOR_DATA:-false} + +# Set of collectors that should be used with IPA inspection +export IRONIC_IPA_COLLECTORS=${IRONIC_IPA_COLLECTORS:-default,logs} + +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}"} +export IRONIC_INSPECTOR_BASE_URL=${IRONIC_INSPECTOR_BASE_URL:-"${IRONIC_INSPECTOR_SCHEME}://${IRONIC_URL_HOST}:${IRONIC_INSPECTOR_ACCESS_PORT}"} + +if [[ -n "$IRONIC_EXTERNAL_IP" ]]; then + export IRONIC_EXTERNAL_CALLBACK_URL="${IRONIC_SCHEME}://${IRONIC_EXTERNAL_IP}:${IRONIC_ACCESS_PORT}" + if [[ "$IRONIC_VMEDIA_TLS_SETUP" == "true" ]]; then + export IRONIC_EXTERNAL_HTTP_URL="https://${IRONIC_EXTERNAL_IP}:${VMEDIA_TLS_PORT}" + else + export IRONIC_EXTERNAL_HTTP_URL="http://${IRONIC_EXTERNAL_IP}:${HTTP_PORT}" + fi + export IRONIC_INSPECTOR_CALLBACK_ENDPOINT_OVERRIDE="https://${IRONIC_EXTERNAL_IP}:${IRONIC_INSPECTOR_ACCESS_PORT}" +fi + +IMAGE_CACHE_PREFIX=/shared/html/images/ironic-python-agent +if [[ -f "${IMAGE_CACHE_PREFIX}.kernel" ]] && [[ -f "${IMAGE_CACHE_PREFIX}.initramfs" ]]; then + export IRONIC_DEFAULT_KERNEL="${IMAGE_CACHE_PREFIX}.kernel" + export IRONIC_DEFAULT_RAMDISK="${IMAGE_CACHE_PREFIX}.initramfs" +fi + +if [[ -f /etc/ironic/ironic.conf ]]; then + # Make a copy of the original supposed empty configuration file + cp /etc/ironic/ironic.conf /etc/ironic/ironic.conf_orig +fi + +# oslo.config also supports Config Opts From Environment, log them to stdout +echo 'Options set from Environment variables' +env | grep "^OS_" || true + +mkdir -p /shared/html +mkdir -p /shared/ironic_prometheus_exporter + +configure_json_rpc_auth + +# 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 + +if [[ "${USE_IRONIC_INSPECTOR}" == "true" ]]; then + configure_client_basic_auth ironic-inspector +fi +configure_client_basic_auth ironic-rpc + +# Make sure ironic traffic bypasses any proxies +export NO_PROXY="${NO_PROXY:-},$IRONIC_IP" diff --git a/ironic-image/configure-nonroot.sh b/ironic-image/configure-nonroot.sh new file mode 100644 index 0000000..caeec02 --- /dev/null +++ b/ironic-image/configure-nonroot.sh @@ -0,0 +1,50 @@ +#!/usr/bin/bash + +NONROOT_UID=10475 +NONROOT_GID=10475 +USER="ironic-suse" + +groupadd -r -g ${NONROOT_GID} ${USER} +useradd -r -g ${NONROOT_GID} \ + -u ${NONROOT_UID} \ + -d /var/lib/ironic \ + -s /sbin/nologin \ + ${USER} + +# create ironic's http_root directory +mkdir -p /shared/html +chown "${NONROOT_UID}":"${NONROOT_GID}" /shared/html + +# we'll bind mount shared ca and ironic/inspector certificate dirs here +# that need to have correct ownership as the entire ironic in BMO +# deployment shares a single fsGroup in manifest's securityContext +mkdir -p /certs/ca +chown "${NONROOT_UID}":"${NONROOT_GID}" /certs{,/ca} +chmod 2775 /certs{,/ca} + +# apache2 permission changes +chown -R "${NONROOT_UID}":"${NONROOT_GID}" /etc/apache2 +chown -R "${NONROOT_UID}":"${NONROOT_GID}" /run + +# ironic, inspector and httpd related changes +chown -R "${NONROOT_UID}":"${NONROOT_GID}" /etc/ironic /etc/httpd /etc/httpd +chown -R "${NONROOT_UID}":"${NONROOT_GID}" /etc/ironic-inspector +chown -R "${NONROOT_UID}":"${NONROOT_GID}" /var/log +chmod 2775 /etc/ironic /etc/ironic-inspector /etc/httpd/conf /etc/httpd/conf.d +chmod 664 /etc/ironic/* /etc/ironic-inspector/* /etc/httpd/conf/* /etc/httpd/conf.d/* + +chown -R "${NONROOT_UID}":"${NONROOT_GID}" /var/lib/ironic +chown -R "${NONROOT_UID}":"${NONROOT_GID}" /var/lib/ironic-inspector +chmod 2775 /var/lib/ironic /var/lib/ironic-inspector +chmod 664 /var/lib/ironic/ironic.db /var/lib/ironic-inspector/ironic-inspector.db + +# dnsmasq, and the capabilities required to run it as non-root user +chown -R "${NONROOT_UID}":"${NONROOT_GID}" /etc/dnsmasq.conf /var/lib/dnsmasq +chmod 2775 /var/lib/dnsmasq +touch /var/lib/dnsmasq/dnsmasq.leases +chmod 664 /etc/dnsmasq.conf /var/lib/dnsmasq/dnsmasq.leases + +# ca-certificates permission changes +touch /var/lib/ca-certificates/ca-bundle.pem.new +chown -R "${NONROOT_UID}":"${NONROOT_GID}" /var/lib/ca-certificates/ +chmod -R +w /var/lib/ca-certificates/ diff --git a/ironic-image/dnsmasq.conf.j2 b/ironic-image/dnsmasq.conf.j2 new file mode 100644 index 0000000..502de9a --- /dev/null +++ b/ironic-image/dnsmasq.conf.j2 @@ -0,0 +1,79 @@ +interface={{ env.PROVISIONING_INTERFACE }} +bind-dynamic +enable-tftp +tftp-root=/shared/tftpboot +log-queries + +# Configure listening for DNS (0 disables DNS) +port={{ env.DNS_PORT }} + +{%- if env.DHCP_RANGE | length %} +log-dhcp +dhcp-range={{ env.DHCP_RANGE }} + +# It can be used when setting DNS or GW variables. +{%- if env["GATEWAY_IP"] is undefined %} +# Disable default router(s) +dhcp-option=3 +{% else %} +dhcp-option=option{% if ":" in env["GATEWAY_IP"] %}6{% endif %}:router,{{ env["GATEWAY_IP"] }} +{% endif %} +{%- if env["DNS_IP"] is undefined %} +# Disable DNS over provisioning network +dhcp-option=6 +{% else %} +dhcp-option=option{% if ":" in env["DNS_IP"] %}6{% endif %}:dns-server,{{ env["DNS_IP"] }} +{% endif %} + +{%- if env.IPV == "4" or env.IPV is undefined %} +# IPv4 Configuration: +dhcp-match=ipxe,175 +# Client is already running iPXE; move to next stage of chainloading +dhcp-boot=tag:ipxe,http://{{ env.IRONIC_URL_HOST }}:{{ env.HTTP_PORT }}/boot.ipxe + +# Note: Need to test EFI booting +dhcp-match=set:efi,option:client-arch,7 +dhcp-match=set:efi,option:client-arch,9 +dhcp-match=set:efi,option:client-arch,11 +# Client is PXE booting over EFI without iPXE ROM; send EFI version of iPXE chainloader +dhcp-boot=tag:efi,tag:!ipxe,snponly.efi + +# Client is running PXE over BIOS; send BIOS version of iPXE chainloader +dhcp-boot=/undionly.kpxe,{{ env.IRONIC_IP }} +{% endif %} + +{% if env.IPV == "6" %} +# IPv6 Configuration: +enable-ra +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 + +# It can be used when setting DNS or GW variables. +{%- if env["GATEWAY_IP"] is undefined %} +# Disable default router(s) +dhcp-option=3 +{% else %} +dhcp-option=3,{{ env["GATEWAY_IP"] }} +{% endif %} +{%- if env["DNS_IP"] is undefined %} +# Disable DNS over provisioning network +dhcp-option=6 +{% else %} +dhcp-option=6,{{ env["DNS_IP"] }} +{% endif %} +{% endif %} +{% endif %} + +{%- if env.DHCP_IGNORE | length %} +dhcp-ignore={{ env.DHCP_IGNORE }} +{% endif %} + +{%- if env.DHCP_HOSTS | length %} +{%- for item in env.DHCP_HOSTS.split(";") %} +dhcp-host={{ item }} +{%- endfor %} +{% endif %} diff --git a/ironic-image/httpd-ironic-api.conf.j2 b/ironic-image/httpd-ironic-api.conf.j2 new file mode 100644 index 0000000..2132c9f --- /dev/null +++ b/ironic-image/httpd-ironic-api.conf.j2 @@ -0,0 +1,85 @@ +# Licensed 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. + + +{% if env.LISTEN_ALL_INTERFACES | lower == "true" %} +Listen {{ env.IRONIC_LISTEN_PORT }} + +{% else %} +Listen {{ env.IRONIC_URL_HOST }}:{{ env.IRONIC_LISTEN_PORT }} + +{% endif %} + + {% if env.IRONIC_REVERSE_PROXY_SETUP | lower == "true" %} + + {% if env.IRONIC_PRIVATE_PORT == "unix" %} + ProxyPass "/" "unix:/shared/ironic.sock|http://127.0.0.1/" + ProxyPassReverse "/" "unix:/shared/ironic.sock|http://127.0.0.1/" + {% else %} + ProxyPass "/" "http://127.0.0.1:{{ env.IRONIC_PRIVATE_PORT }}/" + ProxyPassReverse "/" "http://127.0.0.1:{{ env.IRONIC_PRIVATE_PORT }}/" + {% endif %} + + {% else %} + WSGIDaemonProcess ironic user=ironic group=ironic threads=10 display-name=%{GROUP} + WSGIScriptAlias / /usr/bin/ironic-api-wsgi + {% endif %} + + SetEnv APACHE_RUN_USER ironic-suse + SetEnv APACHE_RUN_GROUP ironic-suse + WSGIProcessGroup ironic-suse + + ErrorLog /dev/stderr + LogLevel debug + CustomLog /dev/stdout combined + +{% if env.IRONIC_TLS_SETUP == "true" %} + SSLEngine on + SSLProtocol {{ env.IRONIC_SSL_PROTOCOL }} + SSLCertificateFile {{ env.IRONIC_CERT_FILE }} + SSLCertificateKeyFile {{ env.IRONIC_KEY_FILE }} +{% endif %} + + {% if env.IRONIC_REVERSE_PROXY_SETUP | lower == "true" %} + + {% if "IRONIC_HTPASSWD" in env and env.IRONIC_HTPASSWD | length %} + AuthType Basic + AuthName "Restricted area" + AuthUserFile "/etc/ironic/htpasswd" + Require valid-user + {% endif %} + + {% else %} + + WSGIProcessGroup ironic + WSGIApplicationGroup %{GLOBAL} + AllowOverride None + + {% if "IRONIC_HTPASSWD" in env and env.IRONIC_HTPASSWD | length %} + AuthType Basic + AuthName "Restricted WSGI area" + AuthUserFile "/etc/ironic/htpasswd" + Require valid-user + {% else %} + Require all granted + {% endif %} + + {% endif %} + + + Require all granted + + + + Require all granted + + diff --git a/ironic-image/httpd-modules.conf b/ironic-image/httpd-modules.conf new file mode 100644 index 0000000..c1c5aaa --- /dev/null +++ b/ironic-image/httpd-modules.conf @@ -0,0 +1,21 @@ +# Bare minimum set of modules +LoadModule log_config_module /usr/lib64/apache2/mod_log_config.so +LoadModule mime_module /usr/lib64/apache2/mod_mime.so +LoadModule dir_module /usr/lib64/apache2/mod_dir.so +LoadModule authz_core_module /usr/lib64/apache2/mod_authz_core.so +#LoadModule unixd_module modules/mod_unixd.so +#LoadModule mpm_event_module modules/mod_mpm_event.so +LoadModule wsgi_module /usr/lib64/apache2/mod_wsgi.so +LoadModule ssl_module /usr/lib64/apache2/mod_ssl.so +LoadModule env_module /usr/lib64/apache2/mod_env.so +LoadModule proxy_module /usr/lib64/apache2/mod_proxy.so +LoadModule proxy_ajp_module /usr/lib64/apache2/mod_proxy_ajp.so +LoadModule proxy_balancer_module /usr/lib64/apache2/mod_proxy_balancer.so +LoadModule proxy_http_module /usr/lib64/apache2/mod_proxy_http.so +LoadModule slotmem_shm_module /usr/lib64/apache2/mod_slotmem_shm.so +LoadModule headers_module /usr/lib64/apache2/mod_headers.so +LoadModule authn_core_module /usr/lib64/apache2/mod_authn_core.so +LoadModule auth_basic_module /usr/lib64/apache2/mod_auth_basic.so +LoadModule authn_file_module /usr/lib64/apache2/mod_authn_file.so +LoadModule authz_user_module /usr/lib64/apache2/mod_authz_user.so +LoadModule access_compat_module /usr/lib64/apache2/mod_access_compat.so diff --git a/ironic-image/httpd.conf.j2 b/ironic-image/httpd.conf.j2 new file mode 100644 index 0000000..16f5470 --- /dev/null +++ b/ironic-image/httpd.conf.j2 @@ -0,0 +1,84 @@ +ServerRoot "/etc/httpd" +{%- 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 +User ironic-suse +Group ironic-suse + + + AllowOverride none + Require all denied + + +DocumentRoot "/shared/html" + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + +{%- if env.HTTPD_SERVE_NODE_IMAGES | lower == "true" %} + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + +{% endif %} + + + DirectoryIndex index.html + + + + Require all denied + + +ErrorLog "/dev/stderr" + +LogLevel warn + + + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined + LogFormat "%h %l %u %t \"%r\" %>s %b" common + + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio + + CustomLog "/dev/stderr" combined + + + + TypesConfig /etc/mime.types + AddType application/x-compress .Z + AddType application/x-gzip .gz .tgz + AddType text/html .shtml + AddOutputFilter INCLUDES .shtml + + +AddDefaultCharset UTF-8 + + + MIMEMagicFile conf/magic + + +PidFile /var/tmp/httpd.pid + +# EnableSendfile directive could speed up deployments but it could also cause +# issues depending on the underlying file system, to learn more: +# https://httpd.apache.org/docs/current/mod/core.html#enablesendfile +{%- if env.HTTPD_ENABLE_SENDFILE | lower == "true" %} +EnableSendfile on +{% endif %} + +# http TRACE can be subjected to abuse and should be disabled +TraceEnable off + +# provide minimal server information +ServerTokens Prod +ServerSignature Off + +IncludeOptional conf.d/*.conf + diff --git a/ironic-image/inspector-apache.conf.j2 b/ironic-image/inspector-apache.conf.j2 new file mode 100644 index 0000000..b0a9d7f --- /dev/null +++ b/ironic-image/inspector-apache.conf.j2 @@ -0,0 +1,57 @@ +# Licensed 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. + + +{% if env.LISTEN_ALL_INTERFACES | lower == "true" %} +Listen {{ env.IRONIC_INSPECTOR_LISTEN_PORT }} + +{% else %} +Listen {{ env.IRONIC_URL_HOST }}:{{ env.IRONIC_INSPECTOR_LISTEN_PORT }} + +{% endif %} + {% if env.IRONIC_INSPECTOR_PRIVATE_PORT == "unix" %} + ProxyPass "/" "unix:/shared/inspector.sock|http://127.0.0.1/" + ProxyPassReverse "/" "unix:/shared/inspector.sock|http://127.0.0.1/" + {% else %} + ProxyPass "/" "http://127.0.0.1:{{ env.IRONIC_INSPECTOR_PRIVATE_PORT }}/" + ProxyPassReverse "/" "http://127.0.0.1:{{ env.IRONIC_INSPECTOR_PRIVATE_PORT }}/" + {% endif %} + + SetEnv APACHE_RUN_USER ironic-suse + SetEnv APACHE_RUN_GROUP ironic-suse + + ErrorLog /dev/stdout + LogLevel debug + CustomLog /dev/stdout combined + + SSLEngine On + SSLProtocol {{ env.IRONIC_SSL_PROTOCOL }} + SSLCertificateFile {{ env.IRONIC_INSPECTOR_CERT_FILE }} + SSLCertificateKeyFile {{ env.IRONIC_INSPECTOR_KEY_FILE }} + + {% if "INSPECTOR_HTPASSWD" in env and env.INSPECTOR_HTPASSWD | length %} + + AuthType Basic + AuthName "Restricted area" + AuthUserFile "/etc/ironic-inspector/htpasswd" + Require valid-user + + + + Require all granted + + + + Require all granted + + {% endif %} + diff --git a/ironic-image/inspector.ipxe.j2 b/ironic-image/inspector.ipxe.j2 new file mode 100644 index 0000000..93f8c75 --- /dev/null +++ b/ironic-image/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 http://{{ env.IRONIC_IP }}:{{ env.HTTP_PORT }}/images/ironic-python-agent.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_INSPECTOR_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_IP }}:{{ env.HTTP_PORT }}/images/ironic-python-agent.initramfs || goto retry_boot +boot diff --git a/ironic-image/ironic-common.sh b/ironic-image/ironic-common.sh new file mode 100644 index 0000000..f388c6b --- /dev/null +++ b/ironic-image/ironic-common.sh @@ -0,0 +1,110 @@ +#!/usr/bin/bash + +set -euxo pipefail + +IRONIC_IP="${IRONIC_IP:-}" +PROVISIONING_INTERFACE="${PROVISIONING_INTERFACE:-}" +PROVISIONING_IP="${PROVISIONING_IP:-}" +PROVISIONING_MACS="${PROVISIONING_MACS:-}" + +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="provisioning" + + if [[ -n "${PROVISIONING_IP}" ]]; then + if ip -br addr show | grep -qi " ${PROVISIONING_IP}/"; 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 + 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 + # 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 + echo "Waiting for ${IRONIC_IP} to be configured on an interface" + sleep 1 + done + else + until [[ -n "$IRONIC_IP" ]]; do + echo "Waiting for ${PROVISIONING_INTERFACE} interface to be configured" + IRONIC_IP="$(ip -br add show scope global up dev "${PROVISIONING_INTERFACE}" | awk '{print $3}' | sed -e 's%/.*%%' | head -n 1)" + export IRONIC_IP + sleep 1 + done + fi + + # If the IP contains a colon, then it's an IPv6 address, and the HTTP + # host needs surrounding with brackets + if [[ "$IRONIC_IP" =~ .*:.* ]]; then + export IPV=6 + export IRONIC_URL_HOST="[$IRONIC_IP]" + else + export IPV=4 + export IRONIC_URL_HOST="$IRONIC_IP" + fi +} + +render_j2_config() +{ + python3 -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 + # 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 + 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 instead of going through an upgrade. + ironic-dbsync --config-file /etc/ironic/ironic.conf create_schema + fi +} + +# Use the special value "unix" for unix sockets +export IRONIC_PRIVATE_PORT=${IRONIC_PRIVATE_PORT:-6388} +export IRONIC_INSPECTOR_PRIVATE_PORT=${IRONIC_INSPECTOR_PRIVATE_PORT:-5049} + +export IRONIC_ACCESS_PORT=${IRONIC_ACCESS_PORT:-6385} +export IRONIC_LISTEN_PORT=${IRONIC_LISTEN_PORT:-$IRONIC_ACCESS_PORT} + +export IRONIC_INSPECTOR_ACCESS_PORT=${IRONIC_INSPECTOR_ACCESS_PORT:-5050} +export IRONIC_INSPECTOR_LISTEN_PORT=${IRONIC_INSPECTOR_LISTEN_PORT:-$IRONIC_INSPECTOR_ACCESS_PORT} + +# If this is false, built-in inspection is used. +export USE_IRONIC_INSPECTOR=${USE_IRONIC_INSPECTOR:-true} +export IRONIC_INSPECTOR_ENABLE_DISCOVERY=${IRONIC_INSPECTOR_ENABLE_DISCOVERY:-false} +if [[ "${USE_IRONIC_INSPECTOR}" != "true" ]] && [[ "${IRONIC_INSPECTOR_ENABLE_DISCOVERY}" == "true" ]]; then + echo "Discovery is only supported with ironic-inspector at this point" + exit 1 +fi diff --git a/ironic-image/ironic-inspector.conf.j2 b/ironic-image/ironic-inspector.conf.j2 new file mode 100644 index 0000000..9932980 --- /dev/null +++ b/ironic-image/ironic-inspector.conf.j2 @@ -0,0 +1,68 @@ +[DEFAULT] +auth_strategy = noauth +debug = true +transport_url = fake:// +use_stderr = true +{% if env.INSPECTOR_REVERSE_PROXY_SETUP == "true" %} +{% if env.IRONIC_INSPECTOR_PRIVATE_PORT == "unix" %} +listen_unix_socket = /shared/inspector.sock +# NOTE(dtantsur): this is not ideal, but since the socket is accessed from +# another container, we need to make it world-writeable. +listen_unix_socket_mode = 0666 +{% else %} +listen_port = {{ env.IRONIC_INSPECTOR_PRIVATE_PORT }} +listen_address = 127.0.0.1 +{% endif %} +{% elif env.LISTEN_ALL_INTERFACES | lower == "true" %} +listen_port = {{ env.IRONIC_INSPECTOR_LISTEN_PORT }} +listen_address = :: +{% else %} +listen_port = {{ env.IRONIC_INSPECTOR_LISTEN_PORT }} +listen_address = {{ env.IRONIC_IP }} +{% endif %} +host = {{ env.IRONIC_IP }} +{% if env.IRONIC_INSPECTOR_TLS_SETUP == "true" and env.INSPECTOR_REVERSE_PROXY_SETUP == "false" %} +use_ssl = true +{% endif %} + +[database] +connection = sqlite:////var/lib/ironic-inspector/ironic-inspector.db + +{% if env.IRONIC_INSPECTOR_ENABLE_DISCOVERY == "true" %} +[discovery] +enroll_node_driver = ipmi +{% endif %} + +[ironic] +auth_type = none +endpoint_override = {{ env.IRONIC_BASE_URL }} +{% if env.IRONIC_TLS_SETUP == "true" %} +cafile = {{ env.IRONIC_CACERT_FILE }} +insecure = {{ env.IRONIC_INSECURE }} +{% endif %} + +[processing] +add_ports = all +always_store_ramdisk_logs = true +keep_ports = present +{% if env.IRONIC_INSPECTOR_ENABLE_DISCOVERY == "true" %} +node_not_found_hook = enroll +{% endif %} +permit_active_introspection = true +power_off = false +processing_hooks = $default_processing_hooks,lldp_basic +ramdisk_logs_dir = /shared/log/ironic-inspector/ramdisk +store_data = database + +[pxe_filter] +driver = noop + +[service_catalog] +auth_type = none +endpoint_override = {{ env.IRONIC_INSPECTOR_BASE_URL }} + +{% if env.IRONIC_INSPECTOR_TLS_SETUP == "true" and env.INSPECTOR_REVERSE_PROXY_SETUP == "false" %} +[ssl] +cert_file = {{ env.IRONIC_INSPECTOR_CERT_FILE }} +key_file = {{ env.IRONIC_INSPECTOR_KEY_FILE }} +{% endif %} diff --git a/ironic-image/ironic.conf.j2 b/ironic-image/ironic.conf.j2 new file mode 100644 index 0000000..5bce6d2 --- /dev/null +++ b/ironic-image/ironic.conf.j2 @@ -0,0 +1,253 @@ +[DEFAULT] +{% if env.AUTH_STRATEGY is defined %} +auth_strategy = {{ env.AUTH_STRATEGY }} +{% if env.AUTH_STRATEGY == "http_basic" %} +http_basic_auth_user_file=/etc/ironic/htpasswd +{% endif %} +{% else %} +auth_strategy = noauth +{% endif %} +debug = true +default_deploy_interface = direct +default_inspect_interface = {% if env.USE_IRONIC_INSPECTOR == "true" %}inspector{% else %}agent{% endif %} +default_network_interface = noop +enabled_bios_interfaces = idrac-wsman,no-bios,redfish,idrac-redfish,irmc,ilo +enabled_boot_interfaces = ipxe,ilo-ipxe,pxe,ilo-pxe,fake,redfish-virtual-media,idrac-redfish-virtual-media,ilo-virtual-media +enabled_deploy_interfaces = direct,fake,ramdisk,custom-agent +# NOTE(dtantsur): when changing this, make sure to update the driver +# dependencies in Dockerfile. +enabled_hardware_types = ipmi,idrac,irmc,fake-hardware,redfish,manual-management,ilo,ilo5 +enabled_inspect_interfaces = {% if env.USE_IRONIC_INSPECTOR == "true" %}inspector{% else %}agent{% endif %},idrac-wsman,irmc,fake,redfish,ilo +enabled_management_interfaces = ipmitool,idrac-wsman,irmc,fake,redfish,idrac-redfish,ilo,ilo5,noop +enabled_power_interfaces = ipmitool,idrac-wsman,irmc,fake,redfish,idrac-redfish,ilo +enabled_raid_interfaces = no-raid,irmc,agent,fake,idrac-wsman,redfish,idrac-redfish,ilo5 +enabled_vendor_interfaces = no-vendor,ipmitool,idrac-wsman,idrac-redfish,redfish,ilo,fake +enabled_firmware_interfaces = no-firmware,fake,redfish +{% if env.IRONIC_EXPOSE_JSON_RPC | lower == "true" %} +rpc_transport = json-rpc +{% else %} +rpc_transport = none +{% endif %} +use_stderr = true +# NOTE(dtantsur): the default md5 is not compatible with FIPS mode +hash_ring_algorithm = sha256 +my_ip = {{ env.IRONIC_IP }} +{% if env.IRONIC_DEPLOYMENT == "Conductor" and env.JSON_RPC_AUTH_STRATEGY == "noauth" %} +# if access is unauthenticated, we bind only to localhost - use that as the +# host name also, so that the client can find the server +# If we run both API and conductor in the same pod, use localhost +host = localhost +{% else %} +host = {{ env.IRONIC_CONDUCTOR_HOST }} +{% endif %} + +# If a path to a certificate is defined, use that first for webserver +{% if env.WEBSERVER_CACERT_FILE %} +webserver_verify_ca = {{ env.WEBSERVER_CACERT_FILE }} +{% elif env.IRONIC_INSECURE == "true" %} +webserver_verify_ca = false +{% endif %} + +isolinux_bin = /usr/share/syslinux/isolinux.bin + +# NOTE(dtantsur): this path is specific to the GRUB image that is built into +# the ESP provided in [conductor]bootloader. +grub_config_path = EFI/BOOT/grub.cfg + +[agent] +deploy_logs_collect = always +deploy_logs_local_path = /shared/log/ironic/deploy +# NOTE(dtantsur): in some environments temporary networking issues can cause +# the whole deployment to fail on inability to reach the ramdisk. Increasing +# 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 + +[api] +{% if env.IRONIC_REVERSE_PROXY_SETUP == "true" %} +{% if env.IRONIC_PRIVATE_PORT == "unix" %} +unix_socket = /shared/ironic.sock +# NOTE(dtantsur): this is not ideal, but since the socket is accessed from +# another container, we need to make it world-writeable. +unix_socket_mode = 0666 +{% else %} +host_ip = 127.0.0.1 +port = {{ env.IRONIC_PRIVATE_PORT }} +{% endif %} +public_endpoint = {{ env.IRONIC_BASE_URL }} +{% else %} +host_ip = {% if env.LISTEN_ALL_INTERFACES | lower == "true" %}::{% else %}{{ env.IRONIC_IP }}{% endif %} +port = {{ env.IRONIC_LISTEN_PORT }} +{% if env.IRONIC_TLS_SETUP == "true" %} +enable_ssl_api = true +{% endif %} +{% endif %} +api_workers = {{ env.NUMWORKERS }} + +# Disable schema validation so we can pass nmstate format +network_data_schema = /etc/ironic/network-data-schema-empty.json + +[conductor] +automated_clean = {{ env.IRONIC_AUTOMATED_CLEAN }} +# NOTE(dtantsur): keep aligned with [pxe]boot_retry_timeout below. +deploy_callback_timeout = 4800 +send_sensor_data = {{ env.SEND_SENSOR_DATA }} +# NOTE(TheJulia): Do not lower this value below 120 seconds. +# 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.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 +# Provide for a timeout longer than 60 seconds for certain vendor's hardware +power_state_change_timeout = 120 +{% if env.IRONIC_DEFAULT_KERNEL is defined %} +deploy_kernel = file://{{ env.IRONIC_DEFAULT_KERNEL }} +{% endif %} +{% if env.IRONIC_DEFAULT_RAMDISK is defined %} +deploy_ramdisk = file://{{ env.IRONIC_DEFAULT_RAMDISK }} +{% endif %} + +[database] +{% if env.IRONIC_USE_MARIADB | lower == "false" %} +connection = sqlite:////var/lib/ironic/ironic.sqlite +# 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] +default_boot_option = local +erase_devices_metadata_priority = 10 +erase_devices_priority = 0 +http_root = /shared/html/ +http_url = {{ env.IRONIC_BOOT_BASE_URL }} +fast_track = {{ env.IRONIC_FAST_TRACK }} +{% if env.IRONIC_BOOT_ISO_SOURCE %} +ramdisk_image_download_source = {{ env.IRONIC_BOOT_ISO_SOURCE }} +{% endif %} +{% if env.IRONIC_EXTERNAL_HTTP_URL %} +external_http_url = {{ env.IRONIC_EXTERNAL_HTTP_URL }} +{% elif env.IRONIC_VMEDIA_TLS_SETUP == "true" %} +external_http_url = https://{{ env.IRONIC_URL_HOST }}:{{ env.VMEDIA_TLS_PORT }} +{% endif %} +{% if env.IRONIC_EXTERNAL_CALLBACK_URL %} +external_callback_url = {{ env.IRONIC_EXTERNAL_CALLBACK_URL }} +{% endif %} + +[dhcp] +dhcp_provider = none + +[inspector] +power_off = {{ false if env.IRONIC_FAST_TRACK == "true" else true }} +# NOTE(dtantsur): keep inspection arguments synchronized with inspector.ipxe +# Also keep in mind that only parameters unique for inspection go here. +# No need to duplicate pxe_append_params/kernel_append_params. +extra_kernel_params = ipa-inspection-collectors={{ env.IRONIC_IPA_COLLECTORS }} ipa-enable-vlan-interfaces={{ env.IRONIC_INSPECTOR_VLAN_INTERFACES }} ipa-inspection-dhcp-all-interfaces=1 ipa-collect-lldp=1 net.ifnames={{ '0' if env.PREDICTABLE_NIC_NAMES == 'false' else '1' }} + +{% if env.USE_IRONIC_INSPECTOR == "true" %} +endpoint_override = {{ env.IRONIC_INSPECTOR_BASE_URL }} +{% if env.IRONIC_INSPECTOR_TLS_SETUP == "true" %} +cafile = {{ env.IRONIC_INSPECTOR_CACERT_FILE }} +insecure = {{ env.IRONIC_INSPECTOR_INSECURE }} +{% endif %} +{% if env.IRONIC_INSPECTOR_CALLBACK_ENDPOINT_OVERRIDE %} +callback_endpoint_override = {{ env.IRONIC_INSPECTOR_CALLBACK_ENDPOINT_OVERRIDE }} +{% endif %} +{% else %} +hooks = $default_hooks,parse-lldp +add_ports = all +keep_ports = present +{% endif %} + +[ipmi] +# use_ipmitool_retries transfers the responsibility of retrying to ipmitool +# when supported. If set to false, then ipmitool is called as follows : +# $ipmitool -R 1 -N 1 ... +# and Ironic handles the retry loop. +use_ipmitool_retries = false +# The following parameters are the defaults in Ironic. They are used in the +# following way if use_ipmitool_retries is set to true: +# $ipmitool -R -N ... +# where : +# X = command_retry_timeout / min_command_interval +# Y = min_command_interval +# If use_ipmitool_retries is false, then ironic retries X times, with an +# interval of Y in between each tries. +min_command_interval = 5 +command_retry_timeout = 60 +# List of possible cipher suites versions that can be +# supported by the hardware in case the field `cipher_suite` +# is not set for the node. (list value) +cipher_suite_versions = 3,17 + +{% if env.IRONIC_EXPOSE_JSON_RPC | lower == "true" %} +[json_rpc] +# We assume that when we run API and conductor in the same container, they use +# authentication over localhost, using the same credentials as API, to prevent +# unauthenticated connections from other processes in the same host since the +# containers are in host networking. +auth_strategy = {{ env.JSON_RPC_AUTH_STRATEGY }} +http_basic_auth_user_file = /etc/ironic/htpasswd-rpc +{% if env.IRONIC_DEPLOYMENT == "Conductor" and env.JSON_RPC_AUTH_STRATEGY == "noauth" %} +# if access is unauthenticated, we bind only to localhost - use that as the +# host name also, so that the client can find the server +host_ip = localhost +{% else %} +host_ip = {% if env.LISTEN_ALL_INTERFACES | lower == "true" %}::{% else %}{{ env.IRONIC_IP }}{% endif %} +{% endif %} +{% if env.IRONIC_TLS_SETUP == "true" %} +use_ssl = true +cafile = {{ env.IRONIC_CACERT_FILE }} +insecure = {{ env.IRONIC_INSECURE }} +{% endif %} +{% endif %} + +[nova] +send_power_notifications = false + +[oslo_messaging_notifications] +driver = prometheus_exporter +location = /shared/ironic_prometheus_exporter +transport_url = fake:// + +[pxe] +# NOTE(dtantsur): keep this value at least 3x lower than +# [conductor]deploy_callback_timeout so that at least some retries happen. +# The default settings enable 3 retries after 20 minutes each. +boot_retry_timeout = 1200 +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.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). +enable_netboot_fallback = true +# Enable the fallback path to in-band inspection +ipxe_fallback_script = inspector.ipxe + +[redfish] +use_swift = false +kernel_append_params = nofb nomodeset vga=normal ipa-insecure={{ env.IPA_INSECURE }} {% 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.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.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 }} + +{% if env.IRONIC_TLS_SETUP == "true" %} +[ssl] +cert_file = {{ env.IRONIC_CERT_FILE }} +key_file = {{ env.IRONIC_KEY_FILE }} +{% endif %} diff --git a/ironic-image/mkisofs_wrapper b/ironic-image/mkisofs_wrapper new file mode 100644 index 0000000..d1d1a3c --- /dev/null +++ b/ironic-image/mkisofs_wrapper @@ -0,0 +1,3 @@ +#!/bin/sh + +xorriso -as mkisofs "${@}" \ No newline at end of file diff --git a/ironic-image/network-data-schema-empty.json b/ironic-image/network-data-schema-empty.json new file mode 100644 index 0000000..d31a3bc --- /dev/null +++ b/ironic-image/network-data-schema-empty.json @@ -0,0 +1 @@ +{} diff --git a/ironic-image/prepare-efi.sh b/ironic-image/prepare-efi.sh new file mode 100644 index 0000000..84e0808 --- /dev/null +++ b/ironic-image/prepare-efi.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -euxo pipefail + +ARCH=$(uname -m) +DEST=${2:-/tmp/esp.img} +OS=${1:-sles} + +BOOTEFI=BOOTX64.efi +GRUBEFI=grubx64.efi + +dd bs=1024 count=6400 if=/dev/zero of=$DEST +mkfs.msdos -F 12 -n 'ESP_IMAGE' $DEST + +mkdir -p /boot/efi/EFI/BOOT +cp -L /usr/lib64/efi/shim.efi /boot/efi/EFI/BOOT/$BOOTEFI +mkdir -p /boot/efi/EFI/$OS +#cp /usr/share/grub2/x86_64-efi/grub.efi /boot/efi/EFI/$OS/$GRUBEFI +cp /usr/share/grub2/x86_64-efi/grub.efi /boot/efi/EFI/$OS/grub.efi + +mmd -i $DEST EFI +mmd -i $DEST EFI/BOOT +mcopy -i $DEST -v /boot/efi/EFI/BOOT/$BOOTEFI ::EFI/BOOT +#mcopy -i $DEST -v /boot/efi/EFI/$OS/$GRUBEFI ::EFI/BOOT +mcopy -i $DEST -v /boot/efi/EFI/$OS/grub.efi ::EFI/BOOT +mdir -i $DEST ::EFI/BOOT; + diff --git a/ironic-image/rundnsmasq b/ironic-image/rundnsmasq new file mode 100644 index 0000000..92af2eb --- /dev/null +++ b/ironic-image/rundnsmasq @@ -0,0 +1,35 @@ +#!/usr/bin/bash + +set -eux + +# shellcheck disable=SC1091 +. /bin/ironic-common.sh + +export HTTP_PORT=${HTTP_PORT:-80} +DNSMASQ_EXCEPT_INTERFACE=${DNSMASQ_EXCEPT_INTERFACE:-lo} +export DNS_PORT=${DNS_PORT:-0} + +wait_for_interface_or_ip +if [[ "${DNS_IP:-}" == "provisioning" ]]; then + export DNS_IP="$IRONIC_URL_HOST" +fi + +mkdir -p /shared/tftpboot +mkdir -p /shared/html/images +mkdir -p /shared/html/pxelinux.cfg + +# Copy files to shared mount +cp /tftpboot/undionly.kpxe /tftpboot/snponly.efi /shared/tftpboot + +# 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 + +for iface in $(echo "$DNSMASQ_EXCEPT_INTERFACE" | tr ',' ' '); do + sed -i -e "/^interface=.*/ a\except-interface=${iface}" /tmp/dnsmasq.conf +done +cat /tmp/dnsmasq.conf > /etc/dnsmasq.conf +rm /tmp/dnsmasq.conf + +exec /usr/sbin/dnsmasq -d -q -C /etc/dnsmasq.conf diff --git a/ironic-image/runhttpd b/ironic-image/runhttpd new file mode 100644 index 0000000..57e7c97 --- /dev/null +++ b/ironic-image/runhttpd @@ -0,0 +1,101 @@ +#!/usr/bin/bash + +# shellcheck disable=SC1091 +. /bin/tls-common.sh +. /bin/ironic-common.sh +. /bin/auth-common.sh + +export HTTP_PORT=${HTTP_PORT:-80} +export VMEDIA_TLS_PORT=${VMEDIA_TLS_PORT:-8083} + +INSPECTOR_ORIG_HTTPD_CONFIG=/etc/httpd/conf.d/inspector-apache.conf.j2 +INSPECTOR_RESULT_HTTPD_CONFIG=/etc/httpd/conf.d/ironic-inspector.conf +export IRONIC_REVERSE_PROXY_SETUP=${IRONIC_REVERSE_PROXY_SETUP:-false} +export INSPECTOR_REVERSE_PROXY_SETUP=${INSPECTOR_REVERSE_PROXY_SETUP:-false} + +# In Metal3 context they are called node images in Ironic context they are +# called user images. +export HTTPD_SERVE_NODE_IMAGES="${HTTPD_SERVE_NODE_IMAGES:-true}" + +# Whether to enable fast_track provisioning or not +IRONIC_FAST_TRACK=${IRONIC_FAST_TRACK:-true} + +# Whether to activate the EnableSendfile apache directive for httpd +HTTPD_ENABLE_SENDFILE="${HTTPD_ENABLE_SENDFILE:-false}" + +# Set of collectors that should be used with IPA inspection +export IRONIC_IPA_COLLECTORS=${IRONIC_IPA_COLLECTORS:-default,logs} + +wait_for_interface_or_ip + +mkdir -p /shared/html +chmod 0777 /shared/html + +IRONIC_BASE_URL="${IRONIC_SCHEME}://${IRONIC_URL_HOST}" + +if [[ "${USE_IRONIC_INSPECTOR}" == "true" ]]; then + INSPECTOR_EXTRA_ARGS=" ipa-inspection-callback-url=${IRONIC_BASE_URL}:${IRONIC_INSPECTOR_ACCESS_PORT}/v1/continue" +else + INSPECTOR_EXTRA_ARGS=" ipa-inspection-callback-url=${IRONIC_BASE_URL}:${IRONIC_ACCESS_PORT}/v1/continue_inspection" +fi + +if [[ "$IRONIC_FAST_TRACK" == "true" ]]; then + INSPECTOR_EXTRA_ARGS+=" ipa-api-url=${IRONIC_BASE_URL}:${IRONIC_ACCESS_PORT}" +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/uefi_esp.img + +# Render the core httpd config +render_j2_config /etc/httpd/conf/httpd.conf.j2 /etc/httpd/conf/httpd.conf + +if [[ "$USE_IRONIC_INSPECTOR" == "true" ]] && [[ "$IRONIC_INSPECTOR_TLS_SETUP" == "true" ]]; then + if [[ "${INSPECTOR_REVERSE_PROXY_SETUP}" == "true" ]]; then + render_j2_config "$INSPECTOR_ORIG_HTTPD_CONFIG" "$INSPECTOR_RESULT_HTTPD_CONFIG" + fi +else + export INSPECTOR_REVERSE_PROXY_SETUP="false" # If TLS is not used, we have no reason to use the reverse proxy +fi + +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 + fi +else + export IRONIC_REVERSE_PROXY_SETUP="false" # If TLS is not used, we have no reason to use the reverse proxy +fi + +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 +fi + +# Set up inotify to kill the container (restart) whenever cert files for ironic inspector change +if [[ "$IRONIC_INSPECTOR_TLS_SETUP" == "true" ]] && [[ "${RESTART_CONTAINER_CERTIFICATE_UPDATED}" == "true" ]]; then + # shellcheck disable=SC2034 + inotifywait -m -e delete_self "${IRONIC_INSPECTOR_CERT_FILE}" | while read -r file event; do + kill -WINCH $(pgrep httpd) + done & +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 + inotifywait -m -e delete_self "${IRONIC_CERT_FILE}" | while read -r file event; do + kill -WINCH $(pgrep httpd) + done & +fi + +# 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 + inotifywait -m -e delete_self "${IRONIC_VMEDIA_CERT_FILE}" | while read -r file event; do + kill -WINCH $(pgrep httpd) + done & +fi + +exec /usr/sbin/httpd -DFOREGROUND -f /etc/httpd/conf/httpd.conf diff --git a/ironic-image/runironic b/ironic-image/runironic new file mode 100644 index 0000000..5dd6ef2 --- /dev/null +++ b/ironic-image/runironic @@ -0,0 +1,25 @@ +#!/usr/bin/bash + +# These settings must go before configure-ironic since it has different +# defaults. +export IRONIC_USE_MARIADB=${IRONIC_USE_MARIADB:-false} +export IRONIC_EXPOSE_JSON_RPC=${IRONIC_EXPOSE_JSON_RPC:-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 + inotifywait -m -e delete_self "${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/runironic-api b/ironic-image/runironic-api new file mode 100644 index 0000000..9deb9ac --- /dev/null +++ b/ironic-image/runironic-api @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +export IRONIC_DEPLOYMENT="API" + +# shellcheck disable=SC1091 +. /bin/configure-ironic.sh + +export IRONIC_REVERSE_PROXY_SETUP=false + +python3 -c 'import os; import sys; import jinja2; sys.stdout.write(jinja2.Template(sys.stdin.read()).render(env=os.environ))' < /tmp/httpd-ironic-api.conf.j2 > /etc/httpd/conf.d/ironic.conf + +# shellcheck disable=SC1091 +. /bin/runhttpd diff --git a/ironic-image/runironic-conductor b/ironic-image/runironic-conductor new file mode 100644 index 0000000..64b2c82 --- /dev/null +++ b/ironic-image/runironic-conductor @@ -0,0 +1,20 @@ +#!/usr/bin/bash + +export IRONIC_DEPLOYMENT="Conductor" + +# 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 + inotifywait -m -e delete_self "${IRONIC_CERT_FILE}" | while read -r file event; do + kill $(pgrep ironic) + done & +fi + +exec /usr/bin/ironic-conductor diff --git a/ironic-image/runironic-exporter b/ironic-image/runironic-exporter new file mode 100644 index 0000000..f2f60f2 --- /dev/null +++ b/ironic-image/runironic-exporter @@ -0,0 +1,12 @@ +#!/usr/bin/bash + +# shellcheck disable=SC1091 +. /bin/configure-ironic.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" + +exec gunicorn -b "${FLASK_RUN_HOST}:${FLASK_RUN_PORT}" -w 4 \ + ironic_prometheus_exporter.app.wsgi:application diff --git a/ironic-image/runironic-inspector b/ironic-image/runironic-inspector new file mode 100644 index 0000000..c43782d --- /dev/null +++ b/ironic-image/runironic-inspector @@ -0,0 +1,62 @@ +#!/usr/bin/bash + +set -euxo pipefail + +CONFIG=/etc/ironic-inspector/ironic-inspector.conf + +export IRONIC_INSPECTOR_ENABLE_DISCOVERY=${IRONIC_INSPECTOR_ENABLE_DISCOVERY:-false} +export INSPECTOR_REVERSE_PROXY_SETUP=${INSPECTOR_REVERSE_PROXY_SETUP:-false} + +# shellcheck disable=SC1091 +. /bin/tls-common.sh +# shellcheck disable=SC1091 +. /bin/ironic-common.sh +# shellcheck disable=SC1091 +. /bin/auth-common.sh + +if [[ "$USE_IRONIC_INSPECTOR" == "false" ]]; then + echo "FATAL: ironic-inspector is disabled via USE_IRONIC_INSPECTOR" + exit 1 +fi + +wait_for_interface_or_ip + +IRONIC_INSPECTOR_PORT=${IRONIC_INSPECTOR_ACCESS_PORT} +if [[ "$IRONIC_INSPECTOR_TLS_SETUP" == "true" ]]; then + if [[ "${INSPECTOR_REVERSE_PROXY_SETUP}" == "true" ]] && [[ "${IRONIC_INSPECTOR_PRIVATE_PORT}" != "unix" ]]; then + IRONIC_INSPECTOR_PORT=$IRONIC_INSPECTOR_PRIVATE_PORT + fi +else + export INSPECTOR_REVERSE_PROXY_SETUP="false" # If TLS is not used, we have no reason to use the reverse proxy +fi + +export IRONIC_INSPECTOR_BASE_URL="${IRONIC_INSPECTOR_SCHEME}://${IRONIC_URL_HOST}:${IRONIC_INSPECTOR_PORT}" +export IRONIC_BASE_URL="${IRONIC_SCHEME}://${IRONIC_URL_HOST}:${IRONIC_ACCESS_PORT}" + +build_j2_config() +{ + local CONFIG_FILE="$1" + python3 -c 'import os; import sys; import jinja2; sys.stdout.write(jinja2.Template(sys.stdin.read()).render(env=os.environ))' < "$CONFIG_FILE.j2" +} + +# Merge with the original configuration file from the package. +build_j2_config "$CONFIG" | crudini --merge "$CONFIG" + +configure_inspector_auth + +configure_client_basic_auth ironic "${CONFIG}" + +ironic-inspector-dbsync --config-file "${CONFIG}" upgrade + +if [[ "$INSPECTOR_REVERSE_PROXY_SETUP" == "false" ]] && [[ "${RESTART_CONTAINER_CERTIFICATE_UPDATED}" == "true" ]]; then + # shellcheck disable=SC2034 + inotifywait -m -e delete_self "${IRONIC_INSPECTOR_CERT_FILE}" | while read -r file event; do + kill $(pgrep ironic) + done & +fi + +# Make sure ironic traffic bypasses any proxies +export NO_PROXY="${NO_PROXY:-},$IRONIC_IP" + +# shellcheck disable=SC2086 +exec /usr/bin/ironic-inspector diff --git a/ironic-image/runlogwatch.sh b/ironic-image/runlogwatch.sh new file mode 100644 index 0000000..8b2124e --- /dev/null +++ b/ironic-image/runlogwatch.sh @@ -0,0 +1,20 @@ +#!/usr/bin/bash + +# Ramdisk logs path +LOG_DIRS=("/shared/log/ironic/deploy" "/shared/log/ironic-inspector/ramdisk") + +while :; do + for LOG_DIR in "${LOG_DIRS[@]}"; do + if ! ls "${LOG_DIR}"/*.tar.gz 1> /dev/null 2>&1; then + continue + fi + + for fn in "${LOG_DIR}"/*.tar.gz; do + echo "************ Contents of $fn ramdisk log file bundle **************" + tar -xOzvvf "$fn" | sed -e "s/^/$(basename "$fn"): /" + rm -f "$fn" + done + done + + sleep 5 +done diff --git a/ironic-image/tls-common.sh b/ironic-image/tls-common.sh new file mode 100644 index 0000000..992f475 --- /dev/null +++ b/ironic-image/tls-common.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +export IRONIC_CERT_FILE=/certs/ironic/tls.crt +export IRONIC_KEY_FILE=/certs/ironic/tls.key +export IRONIC_CACERT_FILE=/certs/ca/ironic/tls.crt +export IRONIC_INSECURE=${IRONIC_INSECURE:-false} +export IRONIC_SSL_PROTOCOL=${IRONIC_SSL_PROTOCOL:-"-ALL +TLSv1.2 +TLSv1.3"} +export IRONIC_VMEDIA_SSL_PROTOCOL=${IRONIC_VMEDIA_SSL_PROTOCOL:-"ALL"} + +export IRONIC_INSPECTOR_CERT_FILE=/certs/ironic-inspector/tls.crt +export IRONIC_INSPECTOR_KEY_FILE=/certs/ironic-inspector/tls.key +export IRONIC_INSPECTOR_CACERT_FILE=/certs/ca/ironic-inspector/tls.crt +export IRONIC_INSPECTOR_INSECURE=${IRONIC_INSPECTOR_INSECURE:-$IRONIC_INSECURE} + +export IRONIC_VMEDIA_CERT_FILE=/certs/vmedia/tls.crt +export IRONIC_VMEDIA_KEY_FILE=/certs/vmedia/tls.key + +export RESTART_CONTAINER_CERTIFICATE_UPDATED=${RESTART_CONTAINER_CERTIFICATE_UPDATED:-"false"} + +export MARIADB_CACERT_FILE=/certs/ca/mariadb/tls.crt + +mkdir -p /certs/ironic +mkdir -p /certs/ironic-inspector +mkdir -p /certs/ca/ironic +mkdir -p /certs/ca/ironic-inspector + +if [[ -f "$IRONIC_CERT_FILE" ]] && [[ ! -f "$IRONIC_KEY_FILE" ]]; then + echo "Missing TLS Certificate key file $IRONIC_KEY_FILE" + exit 1 +fi +if [[ ! -f "$IRONIC_CERT_FILE" ]] && [[ -f "$IRONIC_KEY_FILE" ]]; then + echo "Missing TLS Certificate file $IRONIC_CERT_FILE" + exit 1 +fi + +if [[ -f "$IRONIC_INSPECTOR_CERT_FILE" ]] && [[ ! -f "$IRONIC_INSPECTOR_KEY_FILE" ]]; then + echo "Missing TLS Certificate key file $IRONIC_INSPECTOR_KEY_FILE" + exit 1 +fi +if [[ ! -f "$IRONIC_INSPECTOR_CERT_FILE" ]] && [[ -f "$IRONIC_INSPECTOR_KEY_FILE" ]]; then + echo "Missing TLS Certificate file $IRONIC_INSPECTOR_CERT_FILE" + exit 1 +fi + +if [[ -f "$IRONIC_VMEDIA_CERT_FILE" ]] && [[ ! -f "$IRONIC_VMEDIA_KEY_FILE" ]]; then + echo "Missing TLS Certificate key file $IRONIC_VMEDIA_KEY_FILE" + exit 1 +fi +if [[ ! -f "$IRONIC_VMEDIA_CERT_FILE" ]] && [[ -f "$IRONIC_VMEDIA_KEY_FILE" ]]; then + echo "Missing TLS Certificate file $IRONIC_VMEDIA_CERT_FILE" + exit 1 +fi + +copy_atomic() +{ + local src="$1" + local dest="$2" + local tmpdest + + tmpdest=$(mktemp "$dest.XXX") + cp "$src" "$tmpdest" + # Hard linking is atomic, but only works on the same volume + ln -f "$tmpdest" "$dest" + rm -f "$tmpdest" +} + +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 + copy_atomic "$IRONIC_CERT_FILE" "$IRONIC_CACERT_FILE" + fi +else + export IRONIC_TLS_SETUP="false" + export IRONIC_SCHEME="http" +fi + +if [[ -f "$IRONIC_INSPECTOR_CERT_FILE" ]] || [[ -f "$IRONIC_INSPECTOR_CACERT_FILE" ]]; then + export IRONIC_INSPECTOR_TLS_SETUP="true" + export IRONIC_INSPECTOR_SCHEME="https" + if [[ ! -f "$IRONIC_INSPECTOR_CACERT_FILE" ]]; then + copy_atomic "$IRONIC_INSPECTOR_CERT_FILE" "$IRONIC_INSPECTOR_CACERT_FILE" + fi +else + export IRONIC_INSPECTOR_TLS_SETUP="false" + export IRONIC_INSPECTOR_SCHEME="http" +fi + +if [[ -f "$IRONIC_VMEDIA_CERT_FILE" ]]; then + export IRONIC_VMEDIA_SCHEME="https" + export IRONIC_VMEDIA_TLS_SETUP="true" +else + export IRONIC_VMEDIA_SCHEME="http" + export IRONIC_VMEDIA_TLS_SETUP="false" +fi + +if [[ -f "$MARIADB_CACERT_FILE" ]]; then + export MARIADB_TLS_ENABLED="true" +else + export MARIADB_TLS_ENABLED="false" +fi diff --git a/ironic-ipa-downloader-image/Dockerfile b/ironic-ipa-downloader-image/Dockerfile new file mode 100644 index 0000000..98fa91e --- /dev/null +++ b/ironic-ipa-downloader-image/Dockerfile @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: Apache-2.0 +#!BuildTag: %%IMG_PREFIX%%ironic-ipa-downloader:2.0.0 +#!BuildTag: %%IMG_PREFIX%%ironic-ipa-downloader:2.0.0-%RELEASE% +#!BuildVersion: 15.6 +ARG SLE_VERSION +FROM registry.suse.com/bci/bci-micro:$SLE_VERSION AS micro + +FROM registry.suse.com/bci/bci-base:$SLE_VERSION AS base +COPY --from=micro / /installroot/ +RUN sed -i -e 's%^# rpm.install.excludedocs = no.*%rpm.install.excludedocs = yes%g' /etc/zypp/zypp.conf +RUN zypper --installroot /installroot --non-interactive install --no-recommends openstack-ironic-image-200-x86_64 python311-devel python311 python311-pip tar gawk git curl xz fakeroot shadow sed cpio; zypper -n clean; rm -rf /var/log/* +#RUN zypper --installroot /installroot --non-interactive install --no-recommends sles-release; +RUN cp /usr/bin/getopt /installroot/ + +FROM micro AS final + +# Define labels according to https://en.opensuse.org/Building_derived_containers +# labelprefix=com.suse.application.ironic +LABEL org.opencontainers.image.authors="SUSE LLC (https://www.suse.com/)" +LABEL org.opencontainers.image.title="SLE Based Ironic IPA Downloader Container Image" +LABEL org.opencontainers.image.description="ironic-ipa-downloader based on the SLE Base Container Image." +LABEL org.opencontainers.image.version="2.0.0" +LABEL org.opencontainers.image.url="https://www.suse.com/solutions/edge-computing/" +LABEL org.opencontainers.image.created="%BUILDTIME%" +LABEL org.opencontainers.image.vendor="SUSE LLC" +LABEL org.opensuse.reference="%%IMG_REPO%%/%%IMG_PREFIX%%ironic-ipa-downloader:2.0.0-%RELEASE%" +LABEL org.openbuildservice.disturl="%DISTURL%" +LABEL com.suse.supportlevel="l3" +LABEL com.suse.eula="SUSE Combined EULA February 2024" +LABEL com.suse.lifecycle-url="https://www.suse.com/lifecycle" +LABEL com.suse.image-type="application" +LABEL com.suse.release-stage="released" +# endlabelprefix + +COPY --from=base /installroot / +RUN cp /getopt /usr/bin/ +RUN cp /srv/tftpboot/openstack-ironic-image/initrd.xz /tmp +RUN cp /srv/tftpboot/openstack-ironic-image/openstack-ironic-image*.kernel /tmp +# configure non-root user +COPY configure-nonroot.sh /bin/ +RUN set -euo pipefail; chmod +x /bin/configure-nonroot.sh +RUN set -euo pipefail; /bin/configure-nonroot.sh && rm -f /bin/configure-nonroot.sh +COPY get-resource.sh /usr/local/bin/get-resource.sh + +RUN set -euo pipefail; chmod +x /usr/local/bin/get-resource.sh diff --git a/ironic-ipa-downloader-image/_service b/ironic-ipa-downloader-image/_service new file mode 100644 index 0000000..67ff653 --- /dev/null +++ b/ironic-ipa-downloader-image/_service @@ -0,0 +1,17 @@ + + + + + Dockerfile + %%openstack-ironic-image-200-x86_64_version%% + openstack-ironic-image-200-x86_64 + patch + + + Dockerfile + IMG_PREFIX=$(rpm --macros=/root/.rpmmacros -E %img_prefix) + IMG_PREFIX + IMG_REPO=$(rpm --macros=/root/.rpmmacros -E %img_repo) + IMG_REPO + + diff --git a/ironic-ipa-downloader-image/configure-nonroot.sh b/ironic-ipa-downloader-image/configure-nonroot.sh new file mode 100644 index 0000000..e85754f --- /dev/null +++ b/ironic-ipa-downloader-image/configure-nonroot.sh @@ -0,0 +1,12 @@ +#!/usr/bin/bash + +NONROOT_UID=10475 +NONROOT_GID=10475 +USER="ironic-suse" + +groupadd -r -g ${NONROOT_GID} ${USER} +useradd -r -g ${NONROOT_GID} \ + -u ${NONROOT_UID} \ + -d /home \ + -s /sbin/nologin \ + ${USER} diff --git a/ironic-ipa-downloader-image/get-resource.sh b/ironic-ipa-downloader-image/get-resource.sh new file mode 100644 index 0000000..373553f --- /dev/null +++ b/ironic-ipa-downloader-image/get-resource.sh @@ -0,0 +1,71 @@ +#!/bin/bash -xe +#CACHEURL=http://172.22.0.1/images + +# Check and set http(s)_proxy. Required for cURL to use a proxy +export http_proxy=${http_proxy:-$HTTP_PROXY} +export https_proxy=${https_proxy:-$HTTPS_PROXY} +export no_proxy=${no_proxy:-$NO_PROXY} + +# Which image should we use +if [ -z "${IPA_BASEURI}" ]; then + # SLES BASED IPA - openstack-ironic-image-x86_64 package + mkdir -p /shared/html/images + cp /tmp/initrd.xz /shared/html/images/ironic-python-agent.initramfs + cp /tmp/openstack-ironic-image*.x86_64*.kernel /shared/html/images/ironic-python-agent.kernel +else + FILENAME=ironic-python-agent + FILENAME_EXT=.tar + FFILENAME=$FILENAME$FILENAME_EXT + + mkdir -p /shared/html/images /shared/tmp + cd /shared/html/images + + TMPDIR=$(mktemp -d -p /shared/tmp) + + # If we have a CACHEURL and nothing has yet been downloaded + # get header info from the cache + ls -l + if [ -n "$CACHEURL" -a ! -e $FFILENAME.headers ] ; then + curl -g --verbose --fail -O "$CACHEURL/$FFILENAME.headers" || true + fi + + # Download the most recent version of IPA + if [ -e $FFILENAME.headers ] ; then + ETAG=$(awk '/ETag:/ {print $2}' $FFILENAME.headers | tr -d "\r") + cd $TMPDIR + curl -g --verbose --dump-header $FFILENAME.headers -O $IPA_BASEURI/$FFILENAME --header "If-None-Match: $ETAG" || cp /shared/html/images/$FFILENAME.headers . + # curl didn't download anything because we have the ETag already + # but we don't have it in the images directory + # Its in the cache, go get it + ETAG=$(awk '/ETag:/ {print $2}' $FFILENAME.headers | tr -d "\"\r") + if [ ! -s $FFILENAME -a ! -e /shared/html/images/$FILENAME-$ETAG/$FFILENAME ] ; then + mv /shared/html/images/$FFILENAME.headers . + curl -g --verbose -O "$CACHEURL/$FILENAME-$ETAG/$FFILENAME" + fi + else + cd $TMPDIR + curl -g --verbose --dump-header $FFILENAME.headers -O $IPA_BASEURI/$FFILENAME + fi + + if [ -s $FFILENAME ] ; then + tar -xf $FFILENAME + + ETAG=$(awk '/ETag:/ {print $2}' $FFILENAME.headers | tr -d "\"\r") + cd - + chmod 755 $TMPDIR + mv $TMPDIR $FILENAME-$ETAG + ln -sf $FILENAME-$ETAG/$FFILENAME.headers $FFILENAME.headers + ln -sf $FILENAME-$ETAG/$FILENAME.initramfs $FILENAME.initramfs + ln -sf $FILENAME-$ETAG/$FILENAME.kernel $FILENAME.kernel + else + rm -rf $TMPDIR + fi +fi + +if [ -d "/tmp/ironic-certificates" ]; then + mkdir -p /tmp/ca/tmp-initrd && cd /tmp/ca/tmp-initrd + xz -d -c -k --fast /shared/html/images/ironic-python-agent.initramfs | fakeroot -s ../initrd.fakeroot cpio -i + mkdir -p etc/ironic-python-agent.d/ca-certs + cp /tmp/ironic-certificates/* etc/ironic-python-agent.d/ca-certs/ + find . | fakeroot -i ../initrd.fakeroot cpio -o -H newc | xz --check=crc32 --x86 --lzma2 --fast > /shared/html/images/ironic-python-agent.initramfs +fi \ No newline at end of file