diff --git a/omnia.sh b/omnia.sh index 45394baa38..2628028950 100755 --- a/omnia.sh +++ b/omnia.sh @@ -204,7 +204,7 @@ cleanup_config(){ # Remove the Omnia core configuration. echo -e "${BLUE} Removing Omnia core configuration.${NC}" - rm -rf $omnia_path/omnia/{hosts,input,log,pulp,provision,pcs,ssh_config,tmp,.data} + rm -rf $omnia_path/omnia/{hosts,input,log,pulp,provision,pcs,ssh_config,tmp,.data,build_stream} # Unmount the NFS shared path if the share option is NFS. if [ "$share_option" = "NFS" ] && [ "$nfs_type" = "external" ]; then diff --git a/prepare_oim/prepare_oim.yml b/prepare_oim/prepare_oim.yml index 49bead531f..39d4ea2976 100644 --- a/prepare_oim/prepare_oim.yml +++ b/prepare_oim/prepare_oim.yml @@ -1,4 +1,4 @@ -# Copyright 2025 Dell Inc. or its subsidiaries. All Rights Reserved. +# Copyright 2026 Dell Inc. or its subsidiaries. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -74,7 +74,18 @@ name: deploy_containers/auth tasks_from: generate_ldap_password_hashes.yml -- name: Deploy the pulp container +- name: Load build_stream configuration + hosts: localhost + connection: local + gather_facts: false + tags: always + tasks: + - name: Include build_stream config file + ansible.builtin.include_vars: + file: "{{ input_project_dir }}/build_stream_config.yml" + failed_when: false + +- name: Deploy containers hosts: oim connection: ssh gather_facts: false @@ -135,6 +146,15 @@ tasks_from: reload_pulp_nginx.yml when: hostvars['oim']['pulp_protocol_https'] +- name: Deploy build_stream container + hosts: oim + connection: ssh + gather_facts: false + tags: build_stream + roles: + - role: deploy_containers/build_stream # noqa:role-name[path] + when: hostvars['localhost']['enable_build_stream'] | default(false) | bool + - name: Omnia service deployment hosts: oim connection: ssh diff --git a/prepare_oim/roles/deploy_containers/build_stream/tasks/deploy_build_stream.yml b/prepare_oim/roles/deploy_containers/build_stream/tasks/deploy_build_stream.yml new file mode 100644 index 0000000000..5e20b92ff2 --- /dev/null +++ b/prepare_oim/roles/deploy_containers/build_stream/tasks/deploy_build_stream.yml @@ -0,0 +1,186 @@ +# Copyright 2026 Dell Inc. or its subsidiaries. All Rights Reserved. +# +# 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. + +--- + +# Check and remove existing build_stream container if running +- name: Check if omnia_build_stream service exists + ansible.builtin.systemd: + name: "{{ build_stream_container_name }}.service" + register: build_stream_service_status + failed_when: false + +- name: Stop omnia_build_stream service if running + ansible.builtin.systemd: + name: "{{ build_stream_container_name }}.service" + state: stopped + enabled: false + when: build_stream_service_status.status is defined + failed_when: false + +- name: Check if omnia_build_stream container exists + containers.podman.podman_container_info: + name: "{{ build_stream_container_name }}" + register: existing_container_info + failed_when: false + +- name: Remove existing omnia_build_stream container + containers.podman.podman_container: + name: "{{ build_stream_container_name }}" + state: absent + when: existing_container_info.containers | length > 0 + +# Get metadata and configuration +- name: Get metadata from omnia_core + containers.podman.podman_container_exec: + name: omnia_core + command: cat {{ oim_metadata_file }} + register: metadata_content + changed_when: false + +- name: Extract configuration from metadata + ansible.builtin.set_fact: + omnia_path: "{{ metadata_content.stdout | regex_search('oim_shared_path:\\s*(\\S+)', '\\1') | first }}" + share_option: "{{ metadata_content.stdout | regex_search('omnia_share_option:\\s*(\\S+)', '\\1') | first | default('') }}" + nfs_type: "{{ metadata_content.stdout | regex_search('nfs_type:\\s*(\\S+)', '\\1') | first | default('') }}" + pulp_server_ip: "{{ hostvars['localhost']['admin_nic_ip'] }}" + pulp_password: "{{ hostvars['localhost']['pulp_password'] }}" + no_log: true + +- name: Set SELinux option for volume mounts + ansible.builtin.set_fact: + selinux_option: "{{ ':z' if (share_option != 'NFS' or nfs_type | default('') != 'external') else '' }}" + +- name: Set Pulp base URL + ansible.builtin.set_fact: + pulp_base_url: "https://{{ pulp_server_ip }}:2225" + +# Pull container image +- name: Pull omnia_build_stream image from Docker Hub + containers.podman.podman_image: + name: "{{ build_stream_image_name }}" + tag: "{{ build_stream_image_tag }}" + state: present + register: image_pull_result + +- name: Display image pull result + ansible.builtin.debug: + msg: "{{ build_stream_image_pull_success_msg }}" + verbosity: 2 + when: image_pull_result is succeeded + +# Create required directories +- name: Create log directory for omnia_build_stream + ansible.builtin.file: + path: "{{ build_stream_log_dir }}" + state: directory + mode: "{{ build_stream_dir_mode }}" + +- name: Create SSL certificate directory + ansible.builtin.file: + path: "{{ build_stream_ssl_dir }}" + state: directory + mode: "{{ build_stream_dir_mode }}" + +# Generate SSL certificates +- name: Check if SSL certificates already exist + ansible.builtin.stat: + path: "{{ build_stream_ssl_cert }}" + register: ssl_cert_stat + +- name: Generate self-signed SSL certificate + ansible.builtin.command: | + openssl req -x509 -newkey rsa:4096 -nodes -days {{ build_stream_ssl_days }} + -keyout {{ build_stream_ssl_key }} + -out {{ build_stream_ssl_cert }} + -subj "/C=US/ST=State/L=City/O=Omnia/CN={{ ansible_hostname }}" + -addext "subjectAltName=DNS:{{ ansible_hostname }},DNS:localhost,IP:{{ ansible_default_ipv4.address }}" + when: not ssl_cert_stat.stat.exists + changed_when: true + +- name: Set permissions on SSL certificates + ansible.builtin.file: + path: "{{ item }}" + mode: "{{ build_stream_ssl_file_mode }}" + loop: + - "{{ build_stream_ssl_cert }}" + - "{{ build_stream_ssl_key }}" + +# Deploy container using Quadlet and check deployment status +- name: Deploy build_stream container and check deployment status + block: + - name: Create Quadlet service file + ansible.builtin.template: + src: build_stream.j2 + dest: "{{ build_stream_quadlet_path }}" + mode: "{{ build_stream_quadlet_file_mode }}" + register: quadlet_out + + - name: Reload systemd if Quadlet changed + ansible.builtin.systemd_service: + daemon_reload: true + when: quadlet_out.changed # noqa: no-handler + + - name: Enable and start build_stream service + ansible.builtin.systemd_service: + name: "{{ build_stream_container_name }}.service" + enabled: true + state: started + + # TODO: Uncomment when API server is implemented + # - name: Wait for container to be ready + # ansible.builtin.pause: + # seconds: "{{ container_ready_wait_seconds }}" + # + # - name: Verify API endpoint health + # ansible.builtin.uri: + # url: "{{ build_stream_health_endpoint }}" + # method: GET + # return_content: true + # status_code: "{{ health_check_status_code }}" + # validate_certs: false + # register: health_check + # retries: "{{ health_check_retries }}" + # delay: "{{ health_check_delay }}" + # until: health_check.status == health_check_status_code + + - name: Configure firewall + ansible.builtin.systemd: + name: firewalld + state: started + enabled: true + + - name: Open build_stream port in firewall + ansible.posix.firewalld: + port: "{{ build_stream_firewall_port }}" + permanent: true + state: enabled + immediate: true + + - name: Check if build_stream container is running after deployment + containers.podman.podman_container_info: + name: "{{ build_stream_container_name }}" + register: build_stream_container_status + + - name: Notify user of build_stream container deployment status + ansible.builtin.debug: + msg: "{{ build_stream_container_success_msg }}" + when: + - build_stream_container_status.containers | length > 0 + - build_stream_container_status.containers[0].State.Status == 'running' + + rescue: + - name: Build_stream container deployment failed + ansible.builtin.fail: + msg: "{{ build_stream_container_failure_msg }}" diff --git a/prepare_oim/roles/deploy_containers/build_stream/tasks/main.yml b/prepare_oim/roles/deploy_containers/build_stream/tasks/main.yml new file mode 100644 index 0000000000..d2146a7628 --- /dev/null +++ b/prepare_oim/roles/deploy_containers/build_stream/tasks/main.yml @@ -0,0 +1,20 @@ +# Copyright 2026 Dell Inc. or its subsidiaries. All Rights Reserved. +# +# 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. + +--- + +- name: Deploy omnia_build_stream container + ansible.builtin.include_tasks: deploy_build_stream.yml + tags: + - build_stream diff --git a/prepare_oim/roles/deploy_containers/build_stream/templates/build_stream.j2 b/prepare_oim/roles/deploy_containers/build_stream/templates/build_stream.j2 new file mode 100644 index 0000000000..cf2f6494a8 --- /dev/null +++ b/prepare_oim/roles/deploy_containers/build_stream/templates/build_stream.j2 @@ -0,0 +1,34 @@ +# =============================================================== +# omnia_build_stream Quadlet Service +# FastAPI Service for Omnia Build Stream Automation +# =============================================================== +[Unit] +Description=Omnia Build Stream FastAPI Container +After=omnia_core.service +Requires=omnia_core.service + +[Container] +ContainerName={{ build_stream_container_name }} +HostName={{ build_stream_container_name }} +Image={{ build_stream_image_name }}:{{ build_stream_image_tag }} +Network=host + +# Environment variables +Environment=PULP_BASE_URL={{ pulp_base_url }} +Environment=PULP_USERNAME=admin +Environment=PULP_PASSWORD={{ pulp_password }} +Environment=PULP_VERIFY_SSL=true +Environment=REQUESTS_CA_BUNDLE=/etc/pulp/certs/pulp_webserver.crt +Environment=SSL_CERT_FILE=/etc/pulp/certs/pulp_webserver.crt + +# Volume mounts (shared from omnia_core) +Volume={{ omnia_path }}/omnia:/opt/omnia{{ selinux_option }} +Volume={{ build_stream_log_dir }}:/var/log/omnia_build_stream{{ selinux_option }} +Volume={{ build_stream_ssl_dir }}:/etc/ssl/omnia:ro{{ selinux_option }} +Volume={{ pulp_certs_dir }}:/etc/pulp/certs:ro{{ selinux_option }} + +[Service] +Restart=always + +[Install] +WantedBy=multi-user.target default.target diff --git a/prepare_oim/roles/deploy_containers/build_stream/vars/main.yml b/prepare_oim/roles/deploy_containers/build_stream/vars/main.yml new file mode 100644 index 0000000000..bc90597436 --- /dev/null +++ b/prepare_oim/roles/deploy_containers/build_stream/vars/main.yml @@ -0,0 +1,75 @@ +# Copyright 2026 Dell Inc. or its subsidiaries. All Rights Reserved. +# +# 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. + +--- + +# Build Stream Container Configuration +build_stream_container_name: "omnia_build_stream" + +# OIM metadata file path +oim_metadata_file: "/opt/omnia/.data/oim_metadata.yml" + +# Docker Hub configuration +build_stream_dockerhub_registry: "docker.io/dellhpcomniaaisolution" +build_stream_image_name: "{{ build_stream_dockerhub_registry }}/omnia_build_stream" +build_stream_image_tag: "1.0" +build_stream_port: 443 +build_stream_log_dir: "{{ omnia_path }}/log/build_stream" + +# Directory permissions +build_stream_dir_mode: "0755" + +# SSL certificate configuration +build_stream_ssl_dir: "/opt/omnia/build_stream/ssl" +build_stream_ssl_cert: "{{ build_stream_ssl_dir }}/cert.pem" +build_stream_ssl_key: "{{ build_stream_ssl_dir }}/key.pem" +build_stream_ssl_days: 365 +build_stream_ssl_file_mode: "0600" + +# Pulp certificate configuration +pulp_certs_dir: "/opt/omnia/pulp/settings/certs" +pulp_webserver_cert: "{{ pulp_certs_dir }}/pulp_webserver.crt" + +# Pulp server configuration - will be set dynamically during deployment +pulp_base_url: "https://{{ admin_nic_ip }}:2225" + +# Quadlet service file path +build_stream_quadlet_path: "/etc/containers/systemd/{{ build_stream_container_name }}.container" +build_stream_quadlet_file_mode: "0644" + +# Health check endpoint +build_stream_health_endpoint: "https://localhost:{{ build_stream_port }}/health" +container_ready_wait_seconds: 5 +health_check_retries: 5 +health_check_delay: 10 +health_check_status_code: 200 + +# Firewall configuration +build_stream_firewall_port: "{{ build_stream_port }}/tcp" + +# Messages +build_stream_deployment_success_msg: "omnia_build_stream container deployed successfully" +build_stream_image_pull_success_msg: + - "Successfully pulled image from Docker Hub" + - "Image: {{ build_stream_image_name }}:{{ build_stream_image_tag }}" +build_stream_deployment_status_msg: + - "{{ build_stream_deployment_success_msg }}" + - "Container: {{ build_stream_container_name }}" + - "Image: {{ build_stream_image_name }}:{{ build_stream_image_tag }}" + - "Pulp Server: {{ pulp_base_url }}" +build_stream_container_success_msg: "The {{ build_stream_container_name }} container has been successfully deployed." +build_stream_container_failure_msg: | + The deployment of the {{ build_stream_container_name }} container has failed. To resolve this issue, + please run the utility/oim_cleanup.yml playbook to clean up any existing OIM resources. + After the cleanup, you can re-run the original playbook to deploy the {{ build_stream_container_name }} container successfully. diff --git a/prepare_oim/roles/deploy_containers/common/tasks/omnia_service.yml b/prepare_oim/roles/deploy_containers/common/tasks/omnia_service.yml index 4994e3ecf6..a888a19eec 100644 --- a/prepare_oim/roles/deploy_containers/common/tasks/omnia_service.yml +++ b/prepare_oim/roles/deploy_containers/common/tasks/omnia_service.yml @@ -1,4 +1,4 @@ -# Copyright 2025 Dell Inc. or its subsidiaries. All Rights Reserved. +# Copyright 2026 Dell Inc. or its subsidiaries. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,6 +22,15 @@ auth_service: "omnia_auth.service" when: hostvars['localhost']['openldap_support'] +- name: Initialize build_stream service variable + ansible.builtin.set_fact: + build_stream_service: "" + +- name: Set build_stream service if enabled + ansible.builtin.set_fact: + build_stream_service: "omnia_build_stream.service" + when: hostvars['localhost']['enable_build_stream'] | default(false) | bool + - name: Start network manager services ansible.builtin.systemd: name: "{{ item }}" diff --git a/prepare_oim/roles/deploy_containers/common/templates/omnia.service.j2 b/prepare_oim/roles/deploy_containers/common/templates/omnia.service.j2 index 56628f7907..d47787f282 100644 --- a/prepare_oim/roles/deploy_containers/common/templates/omnia.service.j2 +++ b/prepare_oim/roles/deploy_containers/common/templates/omnia.service.j2 @@ -1,6 +1,6 @@ [Unit] Description=Top-level target for Omnia Core and OpenCHAMI -Requires=omnia_core.service openchami.target pulp.service registry.service minio.service {{ auth_service }} +Requires=omnia_core.service openchami.target pulp.service registry.service minio.service {{ auth_service }} {{ build_stream_service }} After=network.target Wants=network-online.target diff --git a/utils/roles/oim_cleanup/oim_container_cleanup/tasks/cleanup_build_stream.yml b/utils/roles/oim_cleanup/oim_container_cleanup/tasks/cleanup_build_stream.yml new file mode 100644 index 0000000000..fae35bd389 --- /dev/null +++ b/utils/roles/oim_cleanup/oim_container_cleanup/tasks/cleanup_build_stream.yml @@ -0,0 +1,95 @@ +# Copyright 2026 Dell Inc. or its subsidiaries. All Rights Reserved. +# +# 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. +--- +- name: Reload systemd daemon + ansible.builtin.systemd: + daemon_reload: true + +- name: Populate service facts + ansible.builtin.service_facts: + +- name: Select only the omnia_build_stream service name + ansible.builtin.set_fact: + build_stream_services: "{{ ansible_facts['services'].keys() | select('match', '^omnia_build_stream') | list }}" + +- name: Stop all matching omnia_build_stream services + ansible.builtin.systemd_service: + name: "{{ item }}" + state: stopped + loop: "{{ build_stream_services }}" + when: + - item in ansible_facts.services + - ansible_facts.services[item].state == 'running' + +- name: Get omnia_build_stream container files + ansible.builtin.find: + paths: "/etc/containers/systemd/" + patterns: 'omnia_build_stream*' + file_type: file + register: found_files + +- name: Get the list of omnia build_stream paths + ansible.builtin.set_fact: + build_stream_quad_path_list: "{{ found_files.files | map(attribute='path') | list }}" + +- name: Remove omnia_build_stream systemd unit files + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: "{{ build_stream_quad_path_list }}" + +- name: Reload systemd daemon + ansible.builtin.systemd: + daemon_reload: true + +- name: Get info about omnia_build_stream container + containers.podman.podman_container_info: + name: "{{ build_stream_container_name }}" + register: podinfo + failed_when: false + +- name: Stop build_stream container only if it exists + containers.podman.podman_container: + name: "{{ build_stream_container_name }}" + state: stopped + when: podinfo.containers | length > 0 + +- name: Remove omnia_build_stream containers + containers.podman.podman_container: + name: "{{ build_stream_container_name }}" + state: absent + force_delete: true + when: podinfo.containers | length > 0 + +- name: Remove build_stream cleanup directories + ansible.builtin.file: + path: "{{ item }}" + state: absent + force: true + register: directory_deletion + until: directory_deletion is not failed + retries: "{{ max_retries }}" + loop: "{{ build_stream_cleanup_directory }}" + +- name: Check if target file exists + ansible.builtin.stat: + path: "{{ omnia_target }}" + register: p + +- name: Remove all omnia_build_stream services from omnia.target + ansible.builtin.replace: + path: "{{ omnia_target }}" + regexp: "{{ build_stream_container_name }}.service" + replace: '' + when: p.stat.exists diff --git a/utils/roles/oim_cleanup/oim_container_cleanup/tasks/main.yml b/utils/roles/oim_cleanup/oim_container_cleanup/tasks/main.yml index 55baafbec9..c36b3c5cc6 100644 --- a/utils/roles/oim_cleanup/oim_container_cleanup/tasks/main.yml +++ b/utils/roles/oim_cleanup/oim_container_cleanup/tasks/main.yml @@ -1,4 +1,4 @@ -# Copyright 2025 Dell Inc. or its subsidiaries. All Rights Reserved. +# Copyright 2026 Dell Inc. or its subsidiaries. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -43,6 +43,10 @@ ansible.builtin.import_tasks: cleanup_auth.yml tags: auth +- name: Cleanup build_stream container + ansible.builtin.import_tasks: cleanup_build_stream.yml + tags: build_stream + - name: Cleanup common configuration ansible.builtin.import_tasks: cleanup_common.yml tags: common diff --git a/utils/roles/oim_cleanup/oim_container_cleanup/vars/main.yml b/utils/roles/oim_cleanup/oim_container_cleanup/vars/main.yml index 2df22d09be..49623349d9 100644 --- a/utils/roles/oim_cleanup/oim_container_cleanup/vars/main.yml +++ b/utils/roles/oim_cleanup/oim_container_cleanup/vars/main.yml @@ -1,4 +1,4 @@ -# Copyright 2025 Dell Inc. or its subsidiaries. All Rights Reserved. +# Copyright 2026 Dell Inc. or its subsidiaries. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -140,6 +140,14 @@ auth_cleanup_directory: auth_service_container_name: omnia_auth +# Usage: cleanup_build_stream.yml +build_stream_cleanup_directory: + - "{{ omnia_nfs_share }}/log/build_stream" + - "{{ omnia_nfs_share }}/build_stream/ssl" + - "{{ omnia_nfs_share }}/build_stream" + +build_stream_container_name: omnia_build_stream + # Usage: cleanup_note.yml oim_cleanup_note: | [Post-Cleanup Actions Required]