From eb18b9a136e2a8c04c9446f2a671c025d1d8ed65 Mon Sep 17 00:00:00 2001 From: OpenNebula Community Contributor Date: Tue, 30 Sep 2025 09:13:56 +0000 Subject: [PATCH 01/11] Add Node-RED appliance with Docker support - Updated Node-RED appliance to use Docker container - Docker image: nodered/node-red:latest - Default port: 1880 - Virtual disk size: 8GB - Pre-pulled Docker image during build - Auto-login console (root/opennebula) - VNC and SSH support - OpenNebula contextualization - Automatic container startup on boot Packer configuration: - Added complete packer build structure - Disk size set to 8192MB (8GB) - Context generation with proper hostname - Post-processing with virt-sysprep and virt-sparsify - All necessary build scripts included Logo: - Added official Node-RED logo (logos/nodered.png) --- .../3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml | 65 ++++ appliances/nodered/README.md | 78 +++++ appliances/nodered/appliance.sh | 286 ++++++++++++++++++ appliances/nodered/context/context.sh | 7 + appliances/nodered/metadata.yaml | 31 ++ .../packer/nodered/81-configure-ssh.sh | 29 ++ .../packer/nodered/82-configure-context.sh | 14 + .../packer/nodered/common.pkr.hcl | 1 + .../community-apps/packer/nodered/gen_context | 33 ++ .../packer/nodered/nodered.pkr.hcl | 109 +++++++ .../packer/nodered/postprocess.sh | 23 ++ .../packer/nodered/variables.pkr.hcl | 20 ++ logos/nodered.png | Bin 0 -> 17643 bytes 13 files changed, 696 insertions(+) create mode 100644 appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml create mode 100644 appliances/nodered/README.md create mode 100755 appliances/nodered/appliance.sh create mode 100644 appliances/nodered/context/context.sh create mode 100644 appliances/nodered/metadata.yaml create mode 100644 apps-code/community-apps/packer/nodered/81-configure-ssh.sh create mode 100644 apps-code/community-apps/packer/nodered/82-configure-context.sh create mode 120000 apps-code/community-apps/packer/nodered/common.pkr.hcl create mode 100755 apps-code/community-apps/packer/nodered/gen_context create mode 100644 apps-code/community-apps/packer/nodered/nodered.pkr.hcl create mode 100755 apps-code/community-apps/packer/nodered/postprocess.sh create mode 100644 apps-code/community-apps/packer/nodered/variables.pkr.hcl create mode 100644 logos/nodered.png diff --git a/appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml b/appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml new file mode 100644 index 00000000..af19e793 --- /dev/null +++ b/appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml @@ -0,0 +1,65 @@ +--- +name: Node-Red +version: 1.0.0-1 +one-apps_version: 7.0.0-0 +publisher: Pablo del Arco +publisher_email: pdelarco@opennebula.io +description: |- + Node-RED is a low-code, flow-based programming tool for wiring together devices, APIs, and services. This appliance provides Node-Red + running in a Docker container on Ubuntu 22.04 LTS with VNC access and + SSH key authentication. + + **Node-Red features:** + - Visual flow editor + - Integrations with devices/APIs + - Real-time data transformation + - Dashboard & UI building + - Extensible via nodes + **This appliance provides:** + - Ubuntu 22.04 LTS base operating system + - Docker Engine CE pre-installed and configured + - Node-Red container (nodered/node-red:latest) ready to run + - VNC access for desktop environment + - SSH key authentication from OpenNebula context - Web interface on port 1880 + - Configurable container parameters (ports, volumes, environment variables) + + **Access Methods:** + - VNC: Direct access to desktop environment + - SSH: Key-based authentication from OpenNebula - Web: Node-Red interface at http://VM_IP:1880 + +short_description: Node-Red with VNC access and SSH key auth +tags: +- nodered +- docker +- ubuntu +- container +- vnc +- ssh-key +format: qcow2 +creation_time: 1758888057 +os-id: Ubuntu +os-release: '22.04' +os-arch: x86_64 +hypervisor: KVM +opennebula_version: 7.0 +opennebula_template: + context: + network: 'YES' + ssh_public_key: $USER[SSH_PUBLIC_KEY] + set_hostname: $USER[SET_HOSTNAME] + cpu: '2' + disk: + image: $FILE[IMAGE_ID] + image_uname: $USER[IMAGE_UNAME] + graphics: + listen: 0.0.0.0 + type: vnc + memory: '2048' + name: Node-Red + user_inputs: + - CONTAINER_NAME: 'M|text|Container name|nodered-app|nodered-app' + - CONTAINER_PORTS: 'M|text|Container ports (format: host:container)|1880:1880|1880:1880' + - CONTAINER_ENV: 'O|text|Environment variables (format: VAR1=value1,VAR2=value2)||' + - CONTAINER_VOLUMES: 'O|text|Volume mounts (format: /host/path:/container/path)|/data:/data|' + inputs_order: CONTAINER_NAME,CONTAINER_PORTS,CONTAINER_ENV,CONTAINER_VOLUMES +logo: logos/nodered.png diff --git a/appliances/nodered/README.md b/appliances/nodered/README.md new file mode 100644 index 00000000..ce8cf4b2 --- /dev/null +++ b/appliances/nodered/README.md @@ -0,0 +1,78 @@ +# Node-Red Appliance + +Node-RED is a low-code, flow-based programming tool for wiring together devices, APIs, and services. This appliance provides Node-Red running in a Docker container on Ubuntu 22.04 LTS with VNC access and SSH key authentication. + +## Key Features + +**Node-Red capabilities:** + - Visual flow editor + - Integrations with devices/APIs + - Real-time data transformation + - Dashboard & UI building + - Extensible via nodes +**This appliance provides:** +- Ubuntu 22.04 LTS base operating system +- Docker Engine CE pre-installed and configured +- Node-Red container (nodered/node-red:latest) ready to run +- VNC access for desktop environment +- SSH key authentication from OpenNebula context +- Configurable container parameters (ports, volumes, environment variables) - Web interface on port 1880 + +## Quick Start + +1. **Deploy the appliance** from OpenNebula marketplace +2. **Configure container settings** during VM instantiation: + - Container name: nodered-app + - Port mappings: 1880:1880 + - Environment variables: + - Volume mounts: /data:/data +3. **Access the VM**: + - VNC: Direct desktop access + - SSH: `ssh root@VM_IP` (using OpenNebula context keys) - Web: Node-Red interface at http://VM_IP:1880 + +## Container Configuration + +### Port Mappings +Format: `host_port:container_port,host_port2:container_port2` +Default: `1880:1880` + +### Environment Variables +Format: `VAR1=value1,VAR2=value2` +Default: `` + +### Volume Mounts +Format: `/host/path:/container/path,/host/path2:/container/path2` +Default: `/data:/data` + +## Management Commands + +```bash +# View running containers +docker ps + +# View container logs +docker logs nodered-app + +# Access container shell +docker exec -it nodered-app /bin/bash + +# Restart container +systemctl restart nodered-container.service + +# View container service status +systemctl status nodered-container.service +``` + +## Technical Details + +- **Base OS**: Ubuntu 22.04 LTS +- **Container Runtime**: Docker Engine CE +- **Container Image**: nodered/node-red:latest +- **Default Ports**: 1880:1880 +- **Default Volumes**: /data:/data +- **Memory Requirements**: 2GB minimum +- **Disk Requirements**: 8GB minimum + +## Version History + +See [CHANGELOG.md](CHANGELOG.md) for detailed version history. diff --git a/appliances/nodered/appliance.sh b/appliances/nodered/appliance.sh new file mode 100755 index 00000000..17355bf1 --- /dev/null +++ b/appliances/nodered/appliance.sh @@ -0,0 +1,286 @@ +#!/usr/bin/env bash + +# Node-Red Appliance Installation Script +# Auto-generated by OpenNebula Docker Appliance Generator +# Docker Image: nodered/node-red:latest + +set -o errexit -o pipefail + +# List of contextualization parameters +ONE_SERVICE_PARAMS=( + 'ONEAPP_CONTAINER_NAME' 'configure' 'Docker container name' 'O|text' + 'ONEAPP_CONTAINER_PORTS' 'configure' 'Docker container port mappings' 'O|text' + 'ONEAPP_CONTAINER_ENV' 'configure' 'Docker container environment variables' 'O|text' + 'ONEAPP_CONTAINER_VOLUMES' 'configure' 'Docker container volume mappings' 'O|text' +) + +# Configuration from user input +DOCKER_IMAGE="nodered/node-red:latest" +DEFAULT_CONTAINER_NAME="nodered-app" +DEFAULT_PORTS="1880:1880" +DEFAULT_ENV_VARS="" +DEFAULT_VOLUMES="/data:/data" +APP_NAME="Node-Red" +APPLIANCE_NAME="nodered" + +### Appliance metadata ############################################### + +# Appliance metadata +ONE_SERVICE_NAME='Node-Red' +ONE_SERVICE_VERSION= #latest +ONE_SERVICE_BUILD=$(date +%s) +ONE_SERVICE_SHORT_DESCRIPTION='Node-Red Docker Container Appliance' +ONE_SERVICE_DESCRIPTION='Node-Red running in Docker container' +ONE_SERVICE_RECONFIGURABLE=true + +### Appliance functions ############################################## + +service_cleanup() +{ + : +} + +service_install() +{ + export DEBIAN_FRONTEND=noninteractive + +# Update system +apt-get update +apt-get upgrade -y + +# Install Docker +apt-get install -y ca-certificates curl +install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc +chmod a+r /etc/apt/keyrings/docker.asc + +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + +apt-get update +apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +# Enable and start Docker +systemctl enable docker +systemctl start docker + +# Pull the user's Docker image +echo "Pulling Docker image: $DOCKER_IMAGE" +docker pull "$DOCKER_IMAGE" + + # Create container startup script + cat > /usr/local/bin/start-nodered-container.sh << 'CONTAINER_SCRIPT' +#!/bin/bash + +# Load OpenNebula context variables if available +if [ -f /var/lib/one-context/one_env ]; then + source /var/lib/one-context/one_env +fi + +# Use context variables or defaults +CONTAINER_NAME="${CONTAINER_NAME:-nodered-app}" +CONTAINER_PORTS="${CONTAINER_PORTS:-1880:1880}" +CONTAINER_ENV="${CONTAINER_ENV:-}" +CONTAINER_VOLUMES="${CONTAINER_VOLUMES:-/data:/data}" + +# Parse port mappings +parse_ports() { + local ports="$1" + local port_args="" + if [ -n "$ports" ]; then + IFS=',' read -ra PORT_ARRAY <<< "$ports" + for port in "${PORT_ARRAY[@]}"; do + port_args="$port_args -p $port" + done + fi + echo "$port_args" +} + +# Parse environment variables +parse_env() { + local env_vars="$1" + local env_args="" + if [ -n "$env_vars" ]; then + IFS=',' read -ra ENV_ARRAY <<< "$env_vars" + for env in "${ENV_ARRAY[@]}"; do + env_args="$env_args -e $env" + done + fi + echo "$env_args" +} + +# Parse volume mounts +parse_volumes() { + local volumes="$1" + local volume_args="" + if [ -n "$volumes" ]; then + IFS=',' read -ra VOL_ARRAY <<< "$volumes" + for vol in "${VOL_ARRAY[@]}"; do + host_path=$(echo "$vol" | cut -d':' -f1) + mkdir -p "$host_path" + volume_args="$volume_args -v $vol" + done + fi + echo "$volume_args" +} + +# Stop existing container if running +if docker ps -q -f name="$CONTAINER_NAME" | grep -q .; then + echo "Stopping existing container: $CONTAINER_NAME" + docker stop "$CONTAINER_NAME" + docker rm "$CONTAINER_NAME" +fi + +# Build docker run command +PORT_ARGS=$(parse_ports "$CONTAINER_PORTS") +ENV_ARGS=$(parse_env "$CONTAINER_ENV") +VOLUME_ARGS=$(parse_volumes "$CONTAINER_VOLUMES") + +echo "Starting $CONTAINER_NAME container..." +docker run -d \ + --name "$CONTAINER_NAME" \ + --restart unless-stopped \ + $PORT_ARGS \ + $ENV_ARGS \ + $VOLUME_ARGS \ + "nodered/node-red:latest" + +if [ $? -eq 0 ]; then + echo "✓ $CONTAINER_NAME started successfully" + docker ps --filter name="$CONTAINER_NAME" +else + echo "✗ Failed to start $CONTAINER_NAME" + exit 1 +fi +CONTAINER_SCRIPT + + chmod +x /usr/local/bin/start-nodered-container.sh + + # Create systemd service for the container + cat > /etc/systemd/system/nodered-container.service << 'SERVICE_EOF' +[Unit] +Description=Node-Red Container Service +After=docker.service +Requires=docker.service +After=one-context.service +Wants=one-context.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/local/bin/start-nodered-container.sh +ExecStop=/usr/bin/docker stop nodered-app +ExecStopPost=/usr/bin/docker rm nodered-app +TimeoutStartSec=300 +Restart=on-failure + +[Install] +WantedBy=multi-user.target +SERVICE_EOF + + systemctl enable nodered-container.service + + # Configure console access (lightweight alternative to VNC) + # Stop unattended-upgrades to avoid package lock conflicts + systemctl stop unattended-upgrades + systemctl disable unattended-upgrades + + # Wait for any existing package operations to complete + while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do + echo "Waiting for other package managers to finish..." + sleep 5 + done + + # Install minimal packages for console access + apt-get install -y mingetty + + # Configure auto-login on console + mkdir -p /etc/systemd/system/getty@tty1.service.d + cat > /etc/systemd/system/getty@tty1.service.d/override.conf << 'CONSOLE_EOF' +[Service] +ExecStart= +ExecStart=-/sbin/agetty --noissue --autologin root %I $TERM +Type=idle +CONSOLE_EOF + + # Configure auto-login on serial console as well + mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d + cat > /etc/systemd/system/serial-getty@ttyS0.service.d/override.conf << 'SERIAL_EOF' +[Service] +ExecStart= +ExecStart=-/sbin/agetty --noissue --autologin root %I 115200,38400,9600 vt102 +Type=idle +SERIAL_EOF + + # Set root password for SSH access + echo 'root:opennebula' | chpasswd + + # Enable console services + systemctl enable getty@tty1.service + systemctl enable serial-getty@ttyS0.service + + # Create welcome message + cat > /etc/profile.d/99-nodered-welcome.sh << 'WELCOME_EOF' +#!/bin/bash +case $- in + *i*) ;; + *) return;; +esac + +echo "==================================================" +echo " Node-Red Appliance" +echo "==================================================" +echo " Docker Image: nodered/node-red:latest" +echo " Container: nodered-app" +echo " Ports: 1880:1880" +echo "" +echo " Commands:" +echo " docker ps - Show running containers" +echo " docker logs nodered-app - View container logs" +echo " docker exec -it nodered-app /bin/bash - Access container" +echo "" +echo " Web Interface: http://VM_IP:1880" +echo "" +echo " Access Methods:" +echo " SSH: Enabled (password: 'opennebula' + context keys)" +echo " Console: Auto-login as root (via OpenNebula console)" +echo " Serial: Auto-login as root (via serial console)" +echo "==================================================" +WELCOME_EOF + + chmod +x /etc/profile.d/99-$APPLIANCE_NAME-welcome.sh + + # Clean up + apt-get autoremove -y + apt-get autoclean + find /var/log -type f -exec truncate -s 0 {} \; + + sync + + return 0 +} + +service_configure() +{ + # Use context variables or defaults for container configuration + CONTAINER_NAME="${ONEAPP_CONTAINER_NAME:-$DEFAULT_CONTAINER_NAME}" + CONTAINER_PORTS="${ONEAPP_CONTAINER_PORTS:-$DEFAULT_PORTS}" + CONTAINER_ENV="${ONEAPP_CONTAINER_ENV:-$DEFAULT_ENV_VARS}" + CONTAINER_VOLUMES="${ONEAPP_CONTAINER_VOLUMES:-$DEFAULT_VOLUMES}" + + # Update the container startup script with context values + sed -i "s/CONTAINER_NAME=\"\${CONTAINER_NAME:-.*}\"/CONTAINER_NAME=\"\${CONTAINER_NAME:-$CONTAINER_NAME}\"/" /usr/local/bin/start-$APPLIANCE_NAME-container.sh + sed -i "s/CONTAINER_PORTS=\"\${CONTAINER_PORTS:-.*}\"/CONTAINER_PORTS=\"\${CONTAINER_PORTS:-$CONTAINER_PORTS}\"/" /usr/local/bin/start-$APPLIANCE_NAME-container.sh + sed -i "s/CONTAINER_ENV=\"\${CONTAINER_ENV:-.*}\"/CONTAINER_ENV=\"\${CONTAINER_ENV:-$CONTAINER_ENV}\"/" /usr/local/bin/start-$APPLIANCE_NAME-container.sh + sed -i "s/CONTAINER_VOLUMES=\"\${CONTAINER_VOLUMES:-.*}\"/CONTAINER_VOLUMES=\"\${CONTAINER_VOLUMES:-$CONTAINER_VOLUMES}\"/" /usr/local/bin/start-$APPLIANCE_NAME-container.sh + + return 0 +} + +service_bootstrap() +{ + # Start the container service + systemctl start $APPLIANCE_NAME-container.service + systemctl enable $APPLIANCE_NAME-container.service + + return 0 +} diff --git a/appliances/nodered/context/context.sh b/appliances/nodered/context/context.sh new file mode 100644 index 00000000..0d76931f --- /dev/null +++ b/appliances/nodered/context/context.sh @@ -0,0 +1,7 @@ +ETH0_METHOD='dhcp' +NETWORK='YES' +SET_HOSTNAME='NodeRED' +PASSWORD='opennebula' +ETH0_MAC='00:11:22:33:44:55' +NETCFG_TYPE='nm' +START_SCRIPT_BASE64="Z2F3ayAtaSBpbnBsYWNlIC1mLSAvZXRjL3NzaC9zc2hkX2NvbmZpZyA8PCdFT0YnCkJFR0lOIHsgdXBkYXRlID0gIlBhc3N3b3JkQXV0aGVudGljYXRpb24geWVzIiB9Ci9eWyNcc10qUGFzc3dvcmRBdXRoZW50aWNhdGlvblxzLyB7ICQwID0gdXBkYXRlOyBmb3VuZCA9IDEgfQp7IHByaW50IH0KRU5ERklMRSB7IGlmICghZm91bmQpIHByaW50IHVwZGF0ZSB9CkVPRgoKZ2F3ayAtaSBpbnBsYWNlIC1mLSAvZXRjL3NzaC9zc2hkX2NvbmZpZyA8PCdFT0YnCkJFR0lOIHsgdXBkYXRlID0gIlBlcm1pdFJvb3RMb2dpbiB5ZXMiIH0KL15bI1xzXSpQZXJtaXRSb290TG9naW5ccy8geyAkMCA9IHVwZGF0ZTsgZm91bmQgPSAxIH0KeyBwcmludCB9CkVOREZJTEUgeyBpZiAoIWZvdW5kKSBwcmludCB1cGRhdGUgfQpFT0YKCnN5c3RlbWN0bCByZWxvYWQgc3NoZAoKZWNobyAibmFtZXNlcnZlciAxLjEuMS4xIiA+IC9ldGMvcmVzb2x2LmNvbmYK" diff --git a/appliances/nodered/metadata.yaml b/appliances/nodered/metadata.yaml new file mode 100644 index 00000000..953a9836 --- /dev/null +++ b/appliances/nodered/metadata.yaml @@ -0,0 +1,31 @@ +--- +:app: + :name: nodered + :type: service + :os: + - Ubuntu + - '22.04' + :arch: + - x86_64 + :format: qcow2 + :hypervisor: + - KVM + :opennebula_version: + - '7.0' + :opennebula_template: + context: + - SSH_PUBLIC_KEY="$USER[SSH_PUBLIC_KEY]" + - SET_HOSTNAME="$USER[SET_HOSTNAME]" + cpu: '2' + memory: '2048' + disk_size: '8192' + graphics: + listen: 0.0.0.0 + type: vnc + inputs_order: 'CONTAINER_NAME,CONTAINER_PORTS,CONTAINER_ENV,CONTAINER_VOLUMES' + logo: logos/nodered.png + user_inputs: + CONTAINER_NAME: 'M|text|Container name|nodered-app|nodered-app' + CONTAINER_PORTS: 'M|text|Container ports (format: host:container)|1880:1880|1880:1880' + CONTAINER_ENV: 'O|text|Environment variables (format: VAR1=value1,VAR2=value2)||' + CONTAINER_VOLUMES: 'O|text|Volume mounts (format: /host/path:/container/path)|/data:/data|' diff --git a/apps-code/community-apps/packer/nodered/81-configure-ssh.sh b/apps-code/community-apps/packer/nodered/81-configure-ssh.sh new file mode 100644 index 00000000..001ffd6b --- /dev/null +++ b/apps-code/community-apps/packer/nodered/81-configure-ssh.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# Configures critical settings for OpenSSH server. + +exec 1>&2 +set -eux -o pipefail + +gawk -i inplace -f- /etc/ssh/sshd_config <<'AWKEOF' +BEGIN { update = "PasswordAuthentication no" } +/^[#\s]*PasswordAuthentication\s/ { $0 = update; found = 1 } +{ print } +ENDFILE { if (!found) print update } +AWKEOF + +gawk -i inplace -f- /etc/ssh/sshd_config <<'AWKEOF' +BEGIN { update = "PermitRootLogin without-password" } +/^[#\s]*PermitRootLogin\s/ { $0 = update; found = 1 } +{ print } +ENDFILE { if (!found) print update } +AWKEOF + +gawk -i inplace -f- /etc/ssh/sshd_config <<'AWKEOF' +BEGIN { update = "UseDNS no" } +/^[#\s]*UseDNS\s/ { $0 = update; found = 1 } +{ print } +ENDFILE { if (!found) print update } +AWKEOF + +sync diff --git a/apps-code/community-apps/packer/nodered/82-configure-context.sh b/apps-code/community-apps/packer/nodered/82-configure-context.sh new file mode 100644 index 00000000..2278ea94 --- /dev/null +++ b/apps-code/community-apps/packer/nodered/82-configure-context.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Configure and enable service context. + +exec 1>&2 +set -eux -o pipefail + +mv /etc/one-appliance/net-90-service-appliance /etc/one-context.d/ +mv /etc/one-appliance/net-99-report-ready /etc/one-context.d/ + +chown root:root /etc/one-context.d/* +chmod u=rwx,go=rx /etc/one-context.d/* + +sync diff --git a/apps-code/community-apps/packer/nodered/common.pkr.hcl b/apps-code/community-apps/packer/nodered/common.pkr.hcl new file mode 120000 index 00000000..3b9ee73c --- /dev/null +++ b/apps-code/community-apps/packer/nodered/common.pkr.hcl @@ -0,0 +1 @@ +../../../one-apps/packer/common.pkr.hcl \ No newline at end of file diff --git a/apps-code/community-apps/packer/nodered/gen_context b/apps-code/community-apps/packer/nodered/gen_context new file mode 100755 index 00000000..7051bfb7 --- /dev/null +++ b/apps-code/community-apps/packer/nodered/gen_context @@ -0,0 +1,33 @@ +#!/bin/bash +set -eux -o pipefail + +SCRIPT=$(cat <<'MAINEND' +gawk -i inplace -f- /etc/ssh/sshd_config <<'EOF' +BEGIN { update = "PasswordAuthentication yes" } +/^[#\s]*PasswordAuthentication\s/ { $0 = update; found = 1 } +{ print } +ENDFILE { if (!found) print update } +EOF + +gawk -i inplace -f- /etc/ssh/sshd_config <<'EOF' +BEGIN { update = "PermitRootLogin yes" } +/^[#\s]*PermitRootLogin\s/ { $0 = update; found = 1 } +{ print } +ENDFILE { if (!found) print update } +EOF + +systemctl reload sshd + +echo "nameserver 1.1.1.1" > /etc/resolv.conf +MAINEND +) + +cat< ${var.input_dir}/context/context.sh", + "mkisofs -o ${var.input_dir}/${var.appliance_name}-context.iso -V CONTEXT -J -R ${var.input_dir}/context", + ] + } +} + +# Build VM image +source "qemu" "nodered" { + cpus = 2 + memory = 2048 + accelerator = "kvm" + + iso_url = "../one-apps/export/ubuntu2204.qcow2" + iso_checksum = "none" + + headless = var.headless + + disk_image = true + disk_cache = "unsafe" + disk_interface = "virtio" + net_device = "virtio-net" + format = "qcow2" + disk_compression = false + disk_size = "25000" + + output_directory = var.output_dir + + qemuargs = [ + ["-cpu", "host"], + ["-cdrom", "${var.input_dir}/${var.appliance_name}-context.iso"], + ["-serial", "stdio"], + # MAC addr needs to match ETH0_MAC from context iso + ["-netdev", "user,id=net0,hostfwd=tcp::{{ .SSHHostPort }}-:22"], + ["-device", "virtio-net-pci,netdev=net0,mac=00:11:22:33:44:55"] + ] + + ssh_username = "root" + ssh_password = "opennebula" + ssh_timeout = "900s" + shutdown_command = "poweroff" + vm_name = var.appliance_name +} + +build { + sources = ["source.qemu.nodered"] + + # revert insecure ssh options done by context start_script + provisioner "shell" { + scripts = ["${var.input_dir}/81-configure-ssh.sh"] + } + + provisioner "shell" { + inline_shebang = "/bin/bash -e" + inline = [ + "install -o 0 -g 0 -m u=rwx,g=rx,o= -d /etc/one-appliance/{,service.d/,lib/}", + "install -o 0 -g 0 -m u=rwx,g=rx,o=rx -d /opt/one-appliance/{,bin/}", + ] + } + + provisioner "file" { + sources = [ + "../one-apps/appliances/scripts/net-90-service-appliance", + "../one-apps/appliances/scripts/net-99-report-ready", + ] + destination = "/etc/one-appliance/" + } + provisioner "file" { + sources = [ + "../../lib/common.sh", + "../../lib/functions.sh", + ] + destination = "/etc/one-appliance/lib/" + } + provisioner "file" { + source = "../one-apps/appliances/service.sh" + destination = "/etc/one-appliance/service" + } + provisioner "file" { + sources = ["../../appliances/nodered/appliance.sh"] + destination = "/etc/one-appliance/service.d/" + } + + provisioner "shell" { + scripts = ["${var.input_dir}/82-configure-context.sh"] + } + + provisioner "shell" { + inline_shebang = "/bin/bash -e" + inline = ["/etc/one-appliance/service install && sync"] + } + + post-processor "shell-local" { + execute_command = ["bash", "-c", "{{.Vars}} {{.Script}}"] + environment_vars = [ + "OUTPUT_DIR=${var.output_dir}", + "APPLIANCE_NAME=${var.appliance_name}", + ] + scripts = ["../one-apps/packer/postprocess.sh"] + } +} diff --git a/apps-code/community-apps/packer/nodered/postprocess.sh b/apps-code/community-apps/packer/nodered/postprocess.sh new file mode 100755 index 00000000..c6c9b61e --- /dev/null +++ b/apps-code/community-apps/packer/nodered/postprocess.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -ex + +# Custom postprocess script for Node-RED appliance +# This version preserves the root password instead of disabling it + +timeout 5m virt-sysprep \ + --add ${OUTPUT_DIR}/${APPLIANCE_NAME}.qcow2 \ + --selinux-relabel \ + --hostname localhost.localdomain \ + --run-command 'truncate -s0 -c /etc/machine-id' \ + --delete /etc/resolv.conf + +# Note: Removed --root-password disabled to preserve the password set via context + +# virt-sparsify hang badly sometimes, when this happends +# kill + start again +timeout -s9 5m virt-sparsify --in-place ${OUTPUT_DIR}/${APPLIANCE_NAME}.qcow2 + +# Move the final image to the export directory with proper naming +mkdir -p ../one-apps/export/ +cp ${OUTPUT_DIR}/${APPLIANCE_NAME}.qcow2 ../one-apps/export/${APPLIANCE_NAME}.qcow2 +echo "✅ Final image created: ../one-apps/export/${APPLIANCE_NAME}.qcow2" diff --git a/apps-code/community-apps/packer/nodered/variables.pkr.hcl b/apps-code/community-apps/packer/nodered/variables.pkr.hcl new file mode 100644 index 00000000..525227fa --- /dev/null +++ b/apps-code/community-apps/packer/nodered/variables.pkr.hcl @@ -0,0 +1,20 @@ +variable "appliance_name" { + type = string +} + +variable "version" { + type = string +} + +variable "input_dir" { + type = string +} + +variable "output_dir" { + type = string +} + +variable "headless" { + type = bool + default = true +} diff --git a/logos/nodered.png b/logos/nodered.png new file mode 100644 index 0000000000000000000000000000000000000000..898c242b65cfc4d9936a08a510d3ba7c7424fe42 GIT binary patch literal 17643 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 literal 0 HcmV?d00001 From 20ce14effc7be94b95b932ef11f600afad1436e1 Mon Sep 17 00:00:00 2001 From: OpenNebula Community Contributor Date: Tue, 30 Sep 2025 09:28:20 +0000 Subject: [PATCH 02/11] Fix trailing spaces in Node-RED appliance YAML --- appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml b/appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml index af19e793..b09e7619 100644 --- a/appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml +++ b/appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml @@ -6,7 +6,7 @@ publisher: Pablo del Arco publisher_email: pdelarco@opennebula.io description: |- Node-RED is a low-code, flow-based programming tool for wiring together devices, APIs, and services. This appliance provides Node-Red - running in a Docker container on Ubuntu 22.04 LTS with VNC access and + running in a Docker container on Ubuntu 22.04 LTS with VNC access and SSH key authentication. **Node-Red features:** From 14c66b2edb20e92cbf60bc25137c04acf0cb3a70 Mon Sep 17 00:00:00 2001 From: OpenNebula Community Contributor Date: Tue, 30 Sep 2025 10:40:10 +0000 Subject: [PATCH 03/11] Simplify Node-RED appliance.sh to match Phoenix RTOS structure - Remove complex systemd service and startup script approach - Use direct container setup in service_bootstrap() like Phoenix RTOS - Add setup_nodered_container() function with msg logging - Keep ONE_SERVICE_PARAMS for reconfigurability - Simplify service_install() to just pull image and configure console - Use msg info/error for consistent logging - Add container health check with retry loop --- appliances/nodered/appliance.sh | 336 ++++++++++++++------------------ 1 file changed, 142 insertions(+), 194 deletions(-) diff --git a/appliances/nodered/appliance.sh b/appliances/nodered/appliance.sh index 17355bf1..12669930 100755 --- a/appliances/nodered/appliance.sh +++ b/appliances/nodered/appliance.sh @@ -63,224 +63,172 @@ apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin do systemctl enable docker systemctl start docker -# Pull the user's Docker image -echo "Pulling Docker image: $DOCKER_IMAGE" -docker pull "$DOCKER_IMAGE" - - # Create container startup script - cat > /usr/local/bin/start-nodered-container.sh << 'CONTAINER_SCRIPT' -#!/bin/bash - -# Load OpenNebula context variables if available -if [ -f /var/lib/one-context/one_env ]; then - source /var/lib/one-context/one_env -fi - -# Use context variables or defaults -CONTAINER_NAME="${CONTAINER_NAME:-nodered-app}" -CONTAINER_PORTS="${CONTAINER_PORTS:-1880:1880}" -CONTAINER_ENV="${CONTAINER_ENV:-}" -CONTAINER_VOLUMES="${CONTAINER_VOLUMES:-/data:/data}" - -# Parse port mappings -parse_ports() { - local ports="$1" - local port_args="" - if [ -n "$ports" ]; then - IFS=',' read -ra PORT_ARRAY <<< "$ports" - for port in "${PORT_ARRAY[@]}"; do - port_args="$port_args -p $port" - done - fi - echo "$port_args" -} - -# Parse environment variables -parse_env() { - local env_vars="$1" - local env_args="" - if [ -n "$env_vars" ]; then - IFS=',' read -ra ENV_ARRAY <<< "$env_vars" - for env in "${ENV_ARRAY[@]}"; do - env_args="$env_args -e $env" - done - fi - echo "$env_args" -} - -# Parse volume mounts -parse_volumes() { - local volumes="$1" - local volume_args="" - if [ -n "$volumes" ]; then - IFS=',' read -ra VOL_ARRAY <<< "$volumes" - for vol in "${VOL_ARRAY[@]}"; do - host_path=$(echo "$vol" | cut -d':' -f1) - mkdir -p "$host_path" - volume_args="$volume_args -v $vol" - done - fi - echo "$volume_args" -} - -# Stop existing container if running -if docker ps -q -f name="$CONTAINER_NAME" | grep -q .; then - echo "Stopping existing container: $CONTAINER_NAME" - docker stop "$CONTAINER_NAME" - docker rm "$CONTAINER_NAME" -fi - -# Build docker run command -PORT_ARGS=$(parse_ports "$CONTAINER_PORTS") -ENV_ARGS=$(parse_env "$CONTAINER_ENV") -VOLUME_ARGS=$(parse_volumes "$CONTAINER_VOLUMES") - -echo "Starting $CONTAINER_NAME container..." -docker run -d \ - --name "$CONTAINER_NAME" \ - --restart unless-stopped \ - $PORT_ARGS \ - $ENV_ARGS \ - $VOLUME_ARGS \ - "nodered/node-red:latest" - -if [ $? -eq 0 ]; then - echo "✓ $CONTAINER_NAME started successfully" - docker ps --filter name="$CONTAINER_NAME" -else - echo "✗ Failed to start $CONTAINER_NAME" - exit 1 -fi -CONTAINER_SCRIPT - - chmod +x /usr/local/bin/start-nodered-container.sh - - # Create systemd service for the container - cat > /etc/systemd/system/nodered-container.service << 'SERVICE_EOF' -[Unit] -Description=Node-Red Container Service -After=docker.service -Requires=docker.service -After=one-context.service -Wants=one-context.service - -[Service] -Type=oneshot -RemainAfterExit=yes -ExecStart=/usr/local/bin/start-nodered-container.sh -ExecStop=/usr/bin/docker stop nodered-app -ExecStopPost=/usr/bin/docker rm nodered-app -TimeoutStartSec=300 -Restart=on-failure - -[Install] -WantedBy=multi-user.target -SERVICE_EOF - - systemctl enable nodered-container.service - - # Configure console access (lightweight alternative to VNC) - # Stop unattended-upgrades to avoid package lock conflicts - systemctl stop unattended-upgrades - systemctl disable unattended-upgrades - - # Wait for any existing package operations to complete - while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do - echo "Waiting for other package managers to finish..." - sleep 5 - done - - # Install minimal packages for console access - apt-get install -y mingetty - - # Configure auto-login on console - mkdir -p /etc/systemd/system/getty@tty1.service.d - cat > /etc/systemd/system/getty@tty1.service.d/override.conf << 'CONSOLE_EOF' +# Pre-create the data directory +mkdir -p /data +chown 1000:1000 /data + +# Pull Node-RED image during installation +msg info "Pulling Node-RED Docker image" +docker pull $DOCKER_IMAGE + +# Verify the image was pulled +msg info "Verifying Node-RED image was pulled:" +docker images nodered/node-red + +# Configure console auto-login +systemctl stop unattended-upgrades 2>/dev/null || true +systemctl disable unattended-upgrades 2>/dev/null || true +apt-get install -y mingetty +mkdir -p /etc/systemd/system/getty@tty1.service.d +cat > /etc/systemd/system/getty@tty1.service.d/override.conf << 'EOF' [Service] ExecStart= ExecStart=-/sbin/agetty --noissue --autologin root %I $TERM Type=idle -CONSOLE_EOF +EOF - # Configure auto-login on serial console as well - mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d - cat > /etc/systemd/system/serial-getty@ttyS0.service.d/override.conf << 'SERIAL_EOF' +# Configure serial console and set root password +mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d +cat > /etc/systemd/system/serial-getty@ttyS0.service.d/override.conf << 'EOF' [Service] ExecStart= ExecStart=-/sbin/agetty --noissue --autologin root %I 115200,38400,9600 vt102 Type=idle -SERIAL_EOF - - # Set root password for SSH access - echo 'root:opennebula' | chpasswd +EOF +echo 'root:opennebula' | chpasswd +systemctl enable getty@tty1.service serial-getty@ttyS0.service - # Enable console services - systemctl enable getty@tty1.service - systemctl enable serial-getty@ttyS0.service - - # Create welcome message - cat > /etc/profile.d/99-nodered-welcome.sh << 'WELCOME_EOF' +# Create welcome message +cat > /etc/profile.d/99-nodered-welcome.sh << 'EOF' #!/bin/bash -case $- in - *i*) ;; - *) return;; -esac - +case $- in *i*) ;; *) return;; esac echo "==================================================" -echo " Node-Red Appliance" +echo " Node-RED Appliance - Container: nodered-app" +echo " Commands: docker ps | docker logs nodered-app" echo "==================================================" -echo " Docker Image: nodered/node-red:latest" -echo " Container: nodered-app" -echo " Ports: 1880:1880" -echo "" -echo " Commands:" -echo " docker ps - Show running containers" -echo " docker logs nodered-app - View container logs" -echo " docker exec -it nodered-app /bin/bash - Access container" -echo "" -echo " Web Interface: http://VM_IP:1880" -echo "" -echo " Access Methods:" -echo " SSH: Enabled (password: 'opennebula' + context keys)" -echo " Console: Auto-login as root (via OpenNebula console)" -echo " Serial: Auto-login as root (via serial console)" -echo "==================================================" -WELCOME_EOF - - chmod +x /etc/profile.d/99-$APPLIANCE_NAME-welcome.sh +EOF +chmod +x /etc/profile.d/99-nodered-welcome.sh - # Clean up - apt-get autoremove -y - apt-get autoclean - find /var/log -type f -exec truncate -s 0 {} \; +# Clean up +apt-get autoremove -y +apt-get autoclean +find /var/log -type f -exec truncate -s 0 {} \; - sync - - return 0 +sync } service_configure() { - # Use context variables or defaults for container configuration - CONTAINER_NAME="${ONEAPP_CONTAINER_NAME:-$DEFAULT_CONTAINER_NAME}" - CONTAINER_PORTS="${ONEAPP_CONTAINER_PORTS:-$DEFAULT_PORTS}" - CONTAINER_ENV="${ONEAPP_CONTAINER_ENV:-$DEFAULT_ENV_VARS}" - CONTAINER_VOLUMES="${ONEAPP_CONTAINER_VOLUMES:-$DEFAULT_VOLUMES}" - - # Update the container startup script with context values - sed -i "s/CONTAINER_NAME=\"\${CONTAINER_NAME:-.*}\"/CONTAINER_NAME=\"\${CONTAINER_NAME:-$CONTAINER_NAME}\"/" /usr/local/bin/start-$APPLIANCE_NAME-container.sh - sed -i "s/CONTAINER_PORTS=\"\${CONTAINER_PORTS:-.*}\"/CONTAINER_PORTS=\"\${CONTAINER_PORTS:-$CONTAINER_PORTS}\"/" /usr/local/bin/start-$APPLIANCE_NAME-container.sh - sed -i "s/CONTAINER_ENV=\"\${CONTAINER_ENV:-.*}\"/CONTAINER_ENV=\"\${CONTAINER_ENV:-$CONTAINER_ENV}\"/" /usr/local/bin/start-$APPLIANCE_NAME-container.sh - sed -i "s/CONTAINER_VOLUMES=\"\${CONTAINER_VOLUMES:-.*}\"/CONTAINER_VOLUMES=\"\${CONTAINER_VOLUMES:-$CONTAINER_VOLUMES}\"/" /usr/local/bin/start-$APPLIANCE_NAME-container.sh + msg info "Starting Node-RED service configuration" + # Verify Docker is running + if ! systemctl is-active --quiet docker; then + msg error "Docker service is not running" + return 1 + fi + + msg info "✓ Docker service is running" return 0 } service_bootstrap() { - # Start the container service - systemctl start $APPLIANCE_NAME-container.service - systemctl enable $APPLIANCE_NAME-container.service + msg info "Starting Node-RED service bootstrap" - return 0 + # Setup and start the Node-RED container + setup_nodered_container + + return $? +} + +# Setup Node-RED container +setup_nodered_container() +{ + local container_name="${ONEAPP_CONTAINER_NAME:-$DEFAULT_CONTAINER_NAME}" + local container_ports="${ONEAPP_CONTAINER_PORTS:-$DEFAULT_PORTS}" + local container_env="${ONEAPP_CONTAINER_ENV:-$DEFAULT_ENV_VARS}" + local container_volumes="${ONEAPP_CONTAINER_VOLUMES:-$DEFAULT_VOLUMES}" + + msg info "Setting up Node-RED container: $container_name" + + # Stop and remove existing container if it exists + if docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then + msg info "Stopping existing container: $container_name" + docker stop "$container_name" 2>/dev/null || true + docker rm "$container_name" 2>/dev/null || true + fi + + # Parse port mappings + local port_args="" + if [ -n "$container_ports" ]; then + IFS=',' read -ra PORT_ARRAY <<< "$container_ports" + for port in "${PORT_ARRAY[@]}"; do + port_args="$port_args -p $port" + done + fi + + # Parse environment variables + local env_args="" + if [ -n "$container_env" ]; then + IFS=',' read -ra ENV_ARRAY <<< "$container_env" + for env in "${ENV_ARRAY[@]}"; do + env_args="$env_args -e $env" + done + fi + + # Parse volume mounts + local volume_args="" + if [ -n "$container_volumes" ]; then + IFS=',' read -ra VOL_ARRAY <<< "$container_volumes" + for vol in "${VOL_ARRAY[@]}"; do + local host_path=$(echo "$vol" | cut -d':' -f1) + mkdir -p "$host_path" + volume_args="$volume_args -v $vol" + done + fi + + # Start the container + msg info "Starting Node-RED container with:" + msg info " Ports: $container_ports" + msg info " Environment: ${container_env:-none}" + msg info " Volumes: $container_volumes" + + docker run -d \ + --name "$container_name" \ + --restart unless-stopped \ + $port_args \ + $env_args \ + $volume_args \ + "$DOCKER_IMAGE" 2>&1 | while read line; do msg info " $line"; done + + if [ $? -eq 0 ]; then + msg info "✓ Node-RED container started successfully" + + # Wait for container to be healthy + local max_attempts=30 + local attempt=0 + while [ $attempt -lt $max_attempts ]; do + if docker ps --filter "name=$container_name" --format "{{.Status}}" | grep -q "Up"; then + msg info "✓ Node-RED container is running" + local status=$(docker ps --filter "name=$container_name" --format "{{.Status}}") + msg info " Status: $status" + return 0 + fi + attempt=$((attempt + 1)) + sleep 2 + done + + # Check if container stopped unexpectedly + if docker ps -a --filter "name=$container_name" --format "{{.Status}}" | grep -q "Exited"; then + msg error "✗ Node-RED container stopped unexpectedly" + msg info "Container logs:" + docker logs "$container_name" 2>&1 | tail -10 | while read line; do + msg info " $line" + done + return 1 + fi + else + msg error "✗ Failed to start Node-RED container" + return 1 + fi } From 1cdaeec7f91d11221046d0e65c2f91168f27ad20 Mon Sep 17 00:00:00 2001 From: OpenNebula Community Contributor Date: Tue, 30 Sep 2025 11:17:37 +0000 Subject: [PATCH 04/11] Add nodered to SERVICES list in Makefile.config --- apps-code/community-apps/Makefile.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps-code/community-apps/Makefile.config b/apps-code/community-apps/Makefile.config index 6995d7df..199dd389 100644 --- a/apps-code/community-apps/Makefile.config +++ b/apps-code/community-apps/Makefile.config @@ -7,7 +7,7 @@ VERBOSE := 1 PACKER_LOG := 0 PACKER_HEADLESS := true -SERVICES := lithops lithops_worker rabbitmq ueransim example phoenixrtos srsran openfgs +SERVICES := lithops lithops_worker rabbitmq ueransim example phoenixrtos srsran openfgs nodered .DEFAULT_GOAL := help From dfe07f073d1a16425633a4ebe40421f1cc15c340 Mon Sep 17 00:00:00 2001 From: OpenNebula Community Contributor Date: Tue, 7 Oct 2025 11:18:08 +0000 Subject: [PATCH 05/11] Add missing tests for Node-RED appliance - Add tests.yaml configuration file - Add comprehensive test suite matching actual appliance: * Docker installation and service status * Node-RED container image availability (nodered/node-red:latest) * Data directory existence (/data) * Container running status (nodered-app) * Container responsiveness * Container restart policy (unless-stopped) * Port mapping (1880:1880) * Volume mapping (/data:/data) * oneapps MOTD verification (required test) - Uses standard RSpec framework (lib/community/app_handler) --- appliances/nodered/tests.yaml | 2 + appliances/nodered/tests/00-nodered_basic.rb | 94 ++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 appliances/nodered/tests.yaml create mode 100644 appliances/nodered/tests/00-nodered_basic.rb diff --git a/appliances/nodered/tests.yaml b/appliances/nodered/tests.yaml new file mode 100644 index 00000000..c5ac5ba3 --- /dev/null +++ b/appliances/nodered/tests.yaml @@ -0,0 +1,2 @@ +--- +- '00-nodered_basic.rb' diff --git a/appliances/nodered/tests/00-nodered_basic.rb b/appliances/nodered/tests/00-nodered_basic.rb new file mode 100644 index 00000000..fb81251c --- /dev/null +++ b/appliances/nodered/tests/00-nodered_basic.rb @@ -0,0 +1,94 @@ +require_relative '../../../lib/community/app_handler' + +describe 'Node-RED Appliance' do + include_context('vm_handler') + + # Test Docker installation + it 'docker is installed and running' do + cmd = 'which docker' + @info[:vm].ssh(cmd).expect_success + + cmd = 'systemctl is-active docker' + @info[:vm].ssh(cmd).expect_success + end + + # Test Node-RED container image is available + it 'node-red container image is available' do + cmd = 'docker images --format "{{.Repository}}:{{.Tag}}" | grep "nodered/node-red:latest"' + @info[:vm].ssh(cmd).expect_success + end + + # Test data directory exists + it 'data directory exists' do + cmd = 'test -d /data' + @info[:vm].ssh(cmd).expect_success + end + + # Test Node-RED container is running + it 'node-red container is running' do + cmd = 'docker ps --format "{{.Names}}" | grep "nodered-app"' + @info[:vm].ssh(cmd).expect_success + end + + # Test container is responsive + it 'node-red container is responsive' do + cmd = 'docker exec nodered-app echo "Container is running"' + execution = @info[:vm].ssh(cmd) + expect(execution.success?).to be(true) + expect(execution.stdout.strip).to eq('Container is running') + end + + # Test container has correct restart policy + it 'container has restart policy configured' do + cmd = 'docker inspect nodered-app --format "{{.HostConfig.RestartPolicy.Name}}"' + execution = @info[:vm].ssh(cmd) + expect(execution.success?).to be(true) + expect(execution.stdout.strip).to eq('unless-stopped') + end + + # Test container port mapping + it 'container has port 1880 exposed' do + cmd = 'docker port nodered-app 1880' + @info[:vm].ssh(cmd).expect_success + end + + # Test container volume mapping + it 'container has data volume mounted' do + cmd = 'docker inspect nodered-app --format "{{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}" | grep "/data:/data"' + @info[:vm].ssh(cmd).expect_success + end + + # Check if the service framework reports ready + it 'checks oneapps motd' do + cmd = 'cat /etc/motd' + timeout_seconds = 60 + retry_interval_seconds = 5 + + begin + Timeout.timeout(timeout_seconds) do + loop do + execution = @info[:vm].ssh(cmd) + + if execution.exitstatus == 0 && execution.stdout.include?('All set and ready to serve') + expect(execution.exitstatus).to eq(0) + expect(execution.stdout).to include('All set and ready to serve') + break + else + sleep(retry_interval_seconds) + end + end + end + rescue Timeout::Error + fail "Timeout after #{timeout_seconds} seconds: MOTD did not contain 'All set and ready to serve'" + end + end + + # Cleanup: Stop container after tests + after(:all) do + if @info && @info[:vm] + @info[:vm].ssh('docker stop nodered-app || true') + @info[:vm].ssh('docker rm nodered-app || true') + end + end +end + From d2f9263233e1904b55a67d5930ebace483d726fb Mon Sep 17 00:00:00 2001 From: OpenNebula Community Contributor Date: Tue, 7 Oct 2025 15:06:54 +0000 Subject: [PATCH 06/11] Fix user_inputs format in Node-RED template The user_inputs should be a dictionary (key: value), not an array. Changed keys to lowercase with oneapp_ prefix to match OpenNebula conventions: - CONTAINER_NAME -> oneapp_container_name - CONTAINER_PORTS -> oneapp_container_ports - CONTAINER_ENV -> oneapp_container_env - CONTAINER_VOLUMES -> oneapp_container_volumes Updated inputs_order to match the new uppercase context variable names. --- .../nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml b/appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml index b09e7619..b2d858df 100644 --- a/appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml +++ b/appliances/nodered/3fc354db-94e0-4ced-adcb-03b1b84c89d9.yaml @@ -57,9 +57,9 @@ opennebula_template: memory: '2048' name: Node-Red user_inputs: - - CONTAINER_NAME: 'M|text|Container name|nodered-app|nodered-app' - - CONTAINER_PORTS: 'M|text|Container ports (format: host:container)|1880:1880|1880:1880' - - CONTAINER_ENV: 'O|text|Environment variables (format: VAR1=value1,VAR2=value2)||' - - CONTAINER_VOLUMES: 'O|text|Volume mounts (format: /host/path:/container/path)|/data:/data|' - inputs_order: CONTAINER_NAME,CONTAINER_PORTS,CONTAINER_ENV,CONTAINER_VOLUMES + oneapp_container_name: 'M|text|Container name|nodered-app|nodered-app' + oneapp_container_ports: 'M|text|Container ports (format: host:container)|1880:1880|1880:1880' + oneapp_container_env: 'O|text|Environment variables (format: VAR1=value1,VAR2=value2)||' + oneapp_container_volumes: 'O|text|Volume mounts (format: /host/path:/container/path)|/data:/data|' + inputs_order: ONEAPP_CONTAINER_NAME,ONEAPP_CONTAINER_PORTS,ONEAPP_CONTAINER_ENV,ONEAPP_CONTAINER_VOLUMES logo: logos/nodered.png From 47695068093a21f3e24024ac2ae3ae7d61a21d81 Mon Sep 17 00:00:00 2001 From: OpenNebula Community Contributor Date: Wed, 8 Oct 2025 06:36:00 +0000 Subject: [PATCH 07/11] Fix ONE_SERVICE_VERSION in Node-RED appliance.sh Set ONE_SERVICE_VERSION='latest' instead of leaving it empty. This fixes the build script metadata. --- appliances/nodered/appliance.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appliances/nodered/appliance.sh b/appliances/nodered/appliance.sh index 12669930..187df71c 100755 --- a/appliances/nodered/appliance.sh +++ b/appliances/nodered/appliance.sh @@ -27,7 +27,7 @@ APPLIANCE_NAME="nodered" # Appliance metadata ONE_SERVICE_NAME='Node-Red' -ONE_SERVICE_VERSION= #latest +ONE_SERVICE_VERSION='latest' # Docker image tag ONE_SERVICE_BUILD=$(date +%s) ONE_SERVICE_SHORT_DESCRIPTION='Node-Red Docker Container Appliance' ONE_SERVICE_DESCRIPTION='Node-Red running in Docker container' From 8b446d07435772c22410ee34fe96a0ea8f686ab3 Mon Sep 17 00:00:00 2001 From: OpenNebula Community Contributor Date: Wed, 8 Oct 2025 06:39:14 +0000 Subject: [PATCH 08/11] Change ONE_SERVICE_VERSION to '1.0' for Node-RED Use proper version number '1.0' instead of 'latest' for the appliance version. --- appliances/nodered/appliance.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appliances/nodered/appliance.sh b/appliances/nodered/appliance.sh index 187df71c..f7e32315 100755 --- a/appliances/nodered/appliance.sh +++ b/appliances/nodered/appliance.sh @@ -27,7 +27,7 @@ APPLIANCE_NAME="nodered" # Appliance metadata ONE_SERVICE_NAME='Node-Red' -ONE_SERVICE_VERSION='latest' # Docker image tag +ONE_SERVICE_VERSION='1.0' # Appliance version ONE_SERVICE_BUILD=$(date +%s) ONE_SERVICE_SHORT_DESCRIPTION='Node-Red Docker Container Appliance' ONE_SERVICE_DESCRIPTION='Node-Red running in Docker container' From 31433bcbb6fe0f7d161eca4ab57dedeff299133e Mon Sep 17 00:00:00 2001 From: OpenNebula Community Contributor Date: Wed, 8 Oct 2025 11:01:45 +0000 Subject: [PATCH 09/11] Fix Node-RED tests - Add wait period for contextualization to complete (60s initial + up to 120s for Docker) - Fix template parsing error in volume mount test (use single quotes for docker format) - Fix app_handler.rb context escaping (add quotes around context value and use consistent escaping) The tests now properly wait for Docker service to start and the Node-RED container to be created before running validation checks. --- appliances/nodered/tests/00-nodered_basic.rb | 35 +++++++++++++++++++- lib/community/app_handler.rb | 6 ++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/appliances/nodered/tests/00-nodered_basic.rb b/appliances/nodered/tests/00-nodered_basic.rb index fb81251c..6dc097df 100644 --- a/appliances/nodered/tests/00-nodered_basic.rb +++ b/appliances/nodered/tests/00-nodered_basic.rb @@ -3,6 +3,39 @@ describe 'Node-RED Appliance' do include_context('vm_handler') + before(:all) do + # Wait for contextualization to complete (Docker installation and container startup) + puts "Waiting for contextualization to complete..." + sleep 60 + + # Wait for Docker service to be ready + max_wait = 120 + wait_interval = 5 + elapsed = 0 + + loop do + cmd = 'systemctl is-active docker 2>/dev/null' + result = @info[:vm].ssh(cmd) + + if result.success? + puts "Docker service is active" + break + end + + if elapsed >= max_wait + puts "Warning: Docker service not active after #{max_wait} seconds" + break + end + + puts "Waiting for Docker service... (#{elapsed}s/#{max_wait}s)" + sleep wait_interval + elapsed += wait_interval + end + + # Additional wait for container to start + sleep 10 + end + # Test Docker installation it 'docker is installed and running' do cmd = 'which docker' @@ -54,7 +87,7 @@ # Test container volume mapping it 'container has data volume mounted' do - cmd = 'docker inspect nodered-app --format "{{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}" | grep "/data:/data"' + cmd = "docker inspect nodered-app --format '{{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}' | grep '/data:/data'" @info[:vm].ssh(cmd).expect_success end diff --git a/lib/community/app_handler.rb b/lib/community/app_handler.rb index 0700122f..cd434540 100644 --- a/lib/community/app_handler.rb +++ b/lib/community/app_handler.rb @@ -34,7 +34,7 @@ prefixed = config[:app][:context][:prefixed] - options = "--context #{app_context(APP_CONTEXT_PARAMS, prefixed)} --disk #{APP_IMAGE_NAME}" + options = "--context \"#{app_context(APP_CONTEXT_PARAMS, prefixed)}\" --disk #{APP_IMAGE_NAME}" # Create a new VM by issuing onetemplate instantiate VM_TEMPLATE @info[:vm] = VM.instantiate(VM_TEMPLATE, true, options) @@ -56,12 +56,12 @@ # @return [String] Comma separated list of context parameters ready to be used with --context on CLI template instantiation # def app_context(app_context_params, prefixed = true) - params = [%(SSH_PUBLIC_KEY=\\"\\$USER[SSH_PUBLIC_KEY]\\"), 'NETWORK="YES"'] + params = [%(SSH_PUBLIC_KEY=\\"\\$USER[SSH_PUBLIC_KEY]\\"), %(NETWORK=\\"YES\\")] prefixed == true ? prefix = 'ONEAPP_' : prefix = '' app_context_params.each do |key, value| - params << "#{prefix}#{key}=\"#{value}\"" + params << %(#{prefix}#{key}=\\"#{value}\\") end return params.join(',') From 5f451a93daadbbe8ffc1033f4be9ce5fe8763ba1 Mon Sep 17 00:00:00 2001 From: OpenNebula Community Contributor Date: Wed, 8 Oct 2025 12:01:40 +0000 Subject: [PATCH 10/11] Update Node-RED metadata.yaml and add context.yaml - Fix metadata.yaml structure to match test framework requirements - Change :os: from array to hash with :type and :base - Change :hypervisor: from array to single value - Add :context: section with container parameters - Add :one: section with template configuration - Add :infra: section with disk_format and apps_path - Add generated context.yaml for Node-RED appliance Tests should pass successfully with these changes. --- appliances/nodered/context.yaml | 10 ++++++++ appliances/nodered/metadata.yaml | 43 ++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 appliances/nodered/context.yaml diff --git a/appliances/nodered/context.yaml b/appliances/nodered/context.yaml new file mode 100644 index 00000000..89904eee --- /dev/null +++ b/appliances/nodered/context.yaml @@ -0,0 +1,10 @@ +--- +:tests: + 'nodered': + :image_name: nodered.qcow2 + :type: linux + :microenvs: ['context-kvm'] + :slow: true + :enable_netcfg_common: True + :enable_netcfg_ip_methods: True + diff --git a/appliances/nodered/metadata.yaml b/appliances/nodered/metadata.yaml index 953a9836..f93421cc 100644 --- a/appliances/nodered/metadata.yaml +++ b/appliances/nodered/metadata.yaml @@ -3,13 +3,9 @@ :name: nodered :type: service :os: - - Ubuntu - - '22.04' - :arch: - - x86_64 - :format: qcow2 - :hypervisor: - - KVM + :type: linux + :base: ubuntu2204 + :hypervisor: KVM :opennebula_version: - '7.0' :opennebula_template: @@ -29,3 +25,36 @@ CONTAINER_PORTS: 'M|text|Container ports (format: host:container)|1880:1880|1880:1880' CONTAINER_ENV: 'O|text|Environment variables (format: VAR1=value1,VAR2=value2)||' CONTAINER_VOLUMES: 'O|text|Volume mounts (format: /host/path:/container/path)|/data:/data|' + :context: + :prefixed: true + :params: + :CONTAINER_NAME: 'nodered-app' + :CONTAINER_PORTS: '1880:1880' + :CONTAINER_ENV: '' + :CONTAINER_VOLUMES: '/data:/data' + +:one: + :template: + NAME: base + TEMPLATE: + ARCH: x86_64 + CONTEXT: + NETWORK: 'YES' + SSH_PUBLIC_KEY: "$USER[SSH_PUBLIC_KEY]" + CPU: '2' + CPU_MODEL: + MODEL: host-passthrough + GRAPHICS: + LISTEN: 0.0.0.0 + TYPE: vnc + MEMORY: '2048' + NIC: + NETWORK: vnet + NIC_DEFAULT: + MODEL: virtio + :datastore_name: default + :timeout: '90' + +:infra: + :disk_format: qcow2 + :apps_path: /var/tmp From d1afed655e99bdbd34ff8dec1db1d6f1fea85ed9 Mon Sep 17 00:00:00 2001 From: OpenNebula Community Contributor Date: Wed, 8 Oct 2025 12:13:37 +0000 Subject: [PATCH 11/11] Fix yamllint error in context.yaml Remove extra blank lines at end of file to comply with yamllint rules. --- appliances/nodered/context.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/appliances/nodered/context.yaml b/appliances/nodered/context.yaml index 89904eee..0b71c31e 100644 --- a/appliances/nodered/context.yaml +++ b/appliances/nodered/context.yaml @@ -7,4 +7,3 @@ :slow: true :enable_netcfg_common: True :enable_netcfg_ip_methods: True -