From f4564f05174438dc7cad55264a0d719940954352 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 20 Jun 2024 10:21:39 -0400 Subject: [PATCH 01/13] Rebuild initial pass at AIX to avoid complicated rebase --- factories/exporters.go | 2 + factories/exporters_aix.go | 36 +++ factories/processors.go | 2 + factories/processors_aix.go | 36 +++ factories/receivers.go | 2 + factories/receivers_aix.go | 69 +++++ scripts/install/install_unix.sh | 261 ++++++++++++++--- service/observiq-otel-collector.aix.env | 1 + updater/internal/path/path_unix.go | 27 ++ updater/internal/service/service_aix.go | 140 +++++++++ updater/internal/service/service_aix_test.go | 284 +++++++++++++++++++ 11 files changed, 814 insertions(+), 46 deletions(-) create mode 100644 factories/exporters_aix.go create mode 100644 factories/processors_aix.go create mode 100644 factories/receivers_aix.go create mode 100644 service/observiq-otel-collector.aix.env create mode 100644 updater/internal/path/path_unix.go create mode 100644 updater/internal/service/service_aix.go create mode 100644 updater/internal/service/service_aix_test.go diff --git a/factories/exporters.go b/factories/exporters.go index 3b035a324..b9704297d 100644 --- a/factories/exporters.go +++ b/factories/exporters.go @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !aix + package factories import ( diff --git a/factories/exporters_aix.go b/factories/exporters_aix.go new file mode 100644 index 000000000..9cb586f7e --- /dev/null +++ b/factories/exporters_aix.go @@ -0,0 +1,36 @@ +// Copyright observIQ, Inc. +// +// 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. + +//go:build aix + +package factories + +import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter" + "go.opentelemetry.io/collector/exporter" + "go.opentelemetry.io/collector/exporter/loggingexporter" + "go.opentelemetry.io/collector/exporter/nopexporter" + "go.opentelemetry.io/collector/exporter/otlpexporter" + "go.opentelemetry.io/collector/exporter/otlphttpexporter" +) + +// Restricted to a small subset of debugging exporters plus OTLP +// Many exporters aren't compatible with AIX +var defaultExporters = []exporter.Factory{ + fileexporter.NewFactory(), + loggingexporter.NewFactory(), + nopexporter.NewFactory(), + otlpexporter.NewFactory(), + otlphttpexporter.NewFactory(), +} diff --git a/factories/processors.go b/factories/processors.go index 98d10abdc..de6ffd90c 100644 --- a/factories/processors.go +++ b/factories/processors.go @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !aix + package factories import ( diff --git a/factories/processors_aix.go b/factories/processors_aix.go new file mode 100644 index 000000000..a8eb34f8e --- /dev/null +++ b/factories/processors_aix.go @@ -0,0 +1,36 @@ +// Copyright observIQ, Inc. +// +// 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. + +//go:build aix + +package factories + +import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbytraceprocessor" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor" + "go.opentelemetry.io/collector/processor" + "go.opentelemetry.io/collector/processor/batchprocessor" + "go.opentelemetry.io/collector/processor/processortest" +) + +var defaultProcessors = []processor.Factory{ + attributesprocessor.NewFactory(), + batchprocessor.NewFactory(), + processortest.NewNopFactory(), + groupbytraceprocessor.NewFactory(), + resourcedetectionprocessor.NewFactory(), + transformprocessor.NewFactory(), +} diff --git a/factories/receivers.go b/factories/receivers.go index ca3f3e282..61cddf001 100644 --- a/factories/receivers.go +++ b/factories/receivers.go @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !aix + package factories import ( diff --git a/factories/receivers_aix.go b/factories/receivers_aix.go new file mode 100644 index 000000000..13862ff72 --- /dev/null +++ b/factories/receivers_aix.go @@ -0,0 +1,69 @@ +// Copyright observIQ, Inc. +// +// 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. + +//go:build aix + +package factories + +import ( + "github.com/observiq/bindplane-agent/receiver/pluginreceiver" + "github.com/observiq/bindplane-agent/receiver/sapnetweaverreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/collectdreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filelogreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/fluentforwardreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/influxdbreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jmxreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/memcachedreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mysqlreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/nginxreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/opencensusreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/postgresqlreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/rabbitmqreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/saphanareceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/snmpreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlqueryreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/receiver/otlpreceiver" + "go.opentelemetry.io/collector/receiver/receivertest" +) + +// Restricted list. Many receivers aren't supported on AIX. +// Additionally, we're only including those that make sense for +// An "unmanaged" config. +var defaultReceivers = []receiver.Factory{ + collectdreceiver.NewFactory(), + filelogreceiver.NewFactory(), + fluentforwardreceiver.NewFactory(), + hostmetricsreceiver.NewFactory(), + influxdbreceiver.NewFactory(), + jmxreceiver.NewFactory(), + memcachedreceiver.NewFactory(), + mysqlreceiver.NewFactory(), + nginxreceiver.NewFactory(), + opencensusreceiver.NewFactory(), + otlpreceiver.NewFactory(), + pluginreceiver.NewFactory(), + postgresqlreceiver.NewFactory(), + rabbitmqreceiver.NewFactory(), + receivertest.NewNopFactory(), + saphanareceiver.NewFactory(), + sapnetweaverreceiver.NewFactory(), + snmpreceiver.NewFactory(), + sqlqueryreceiver.NewFactory(), + sqlserverreceiver.NewFactory(), + statsdreceiver.NewFactory(), +} diff --git a/scripts/install/install_unix.sh b/scripts/install/install_unix.sh index c43e8dd67..216a272db 100755 --- a/scripts/install/install_unix.sh +++ b/scripts/install/install_unix.sh @@ -24,48 +24,55 @@ if command -v systemctl > /dev/null 2>&1; then SVC_PRE=systemctl elif command -v service > /dev/null 2>&1; then SVC_PRE=service +elif command -v mkssys > /dev/null 2>&1; then + SVC_PRE=mkssys fi # Script Constants COLLECTOR_USER="observiq-otel-collector" TMP_DIR=${TMPDIR:-"/tmp"} # Allow this to be overriden by cannonical TMPDIR env var MANAGEMENT_YML_PATH="/opt/observiq-otel-collector/manager.yaml" -PREREQS="curl printf $SVC_PRE sed uname cut" +PREREQS="curl printf $SVC_PRE sed uname cut tr tar sudo" SCRIPT_NAME="$0" INDENT_WIDTH=' ' indent="" non_interactive=false error_mode=false -# out_file_path is the full path to the downloaded package (e.g. "/tmp/observiq-otel-collector_linux_amd64.deb") +# Require bash for AIX to create a standardized environment +if [ "$(uname -s)" != "AIX" ]; then + PREREQS="bash $PREREQS" +fi + +# out_file_path is the full path to the downloaded package (e.g. "/tmp/observiq-otel-collector_{os}_amd64.deb") out_file_path="unknown" +os="unknown" +os_arch="unknown" # Colors -if [ "$non_interactive" = "false" ]; then - num_colors=$(tput colors 2>/dev/null) - if test -n "$num_colors" && test "$num_colors" -ge 8; then - bold="$(tput bold)" - underline="$(tput smul)" - # standout can be bold or reversed colors dependent on terminal - standout="$(tput smso)" - reset="$(tput sgr0)" - bg_black="$(tput setab 0)" - bg_blue="$(tput setab 4)" - bg_cyan="$(tput setab 6)" - bg_green="$(tput setab 2)" - bg_magenta="$(tput setab 5)" - bg_red="$(tput setab 1)" - bg_white="$(tput setab 7)" - bg_yellow="$(tput setab 3)" - fg_black="$(tput setaf 0)" - fg_blue="$(tput setaf 4)" - fg_cyan="$(tput setaf 6)" - fg_green="$(tput setaf 2)" - fg_magenta="$(tput setaf 5)" - fg_red="$(tput setaf 1)" - fg_white="$(tput setaf 7)" - fg_yellow="$(tput setaf 3)" - fi +num_colors=$(tput colors 2>/dev/null) +if [ "$non_interactive" = "false" ] && ( [ "$(uname -s)" != AIX ] && test -n "$num_colors" && test "$num_colors" -ge 8 ); then + bold="$(tput bold)" + underline="$(tput smul)" + # standout can be bold or reversed colors dependent on terminal + standout="$(tput smso)" + reset="$(tput sgr0)" + bg_black="$(tput setab 0)" + bg_blue="$(tput setab 4)" + bg_cyan="$(tput setab 6)" + bg_green="$(tput setab 2)" + bg_magenta="$(tput setab 5)" + bg_red="$(tput setab 1)" + bg_white="$(tput setab 7)" + bg_yellow="$(tput setab 3)" + fg_black="$(tput setaf 0)" + fg_blue="$(tput setaf 4)" + fg_cyan="$(tput setaf 6)" + fg_green="$(tput setaf 2)" + fg_magenta="$(tput setaf 5)" + fg_red="$(tput setaf 1)" + fg_white="$(tput setaf 7)" + fg_yellow="$(tput setaf 3)" fi if [ -z "$reset" ]; then @@ -77,7 +84,7 @@ fi # Helper Functions printf() { if [ "$non_interactive" = "false" ] || [ "$error_mode" = "true" ]; then - if command -v sed >/dev/null; then + if [ "$(uname -s)" != "AIX" ] && command -v sed >/dev/null; then command printf -- "$@" | sed -r "$sed_ignore s/^/$indent/g" # Ignore sole reset characters if defined else # Ignore $* suggestion as this breaks the output @@ -189,7 +196,7 @@ Usage: Example: '-l http://my.domain.org/observiq-otel-collector' will download from there. $(fg_yellow '-b, --base-url') - Defines the base of the download URL as '{base_url}/v{version}/{PACKAGE_NAME}_v{version}_linux_{os_arch}.{package_type}'. + Defines the base of the download URL as '{base_url}/v{version}/{PACKAGE_NAME}_v{version}_{os}_{os_arch}.{package_type}'. If not provided, this will default to '$DOWNLOAD_BASE'. Example: '-b http://my.domain.org/observiq-otel-collector/binaries' will be used as the base of the download URL. @@ -326,9 +333,9 @@ setup_installation() set_file_name() { if [ -z "$version" ] ; then - package_file_name="${PACKAGE_NAME}_linux_${arch}.${package_type}" + package_file_name="${PACKAGE_NAME}_${os}_${os_arch}.${package_type}" else - package_file_name="${PACKAGE_NAME}_v${version}_linux_${arch}.${package_type}" + package_file_name="${PACKAGE_NAME}_v${version}_${os}_${os_arch}.${package_type}" fi out_file_path="$TMP_DIR/$package_file_name" } @@ -364,7 +371,6 @@ set_proxy() set_os_arch() { - os_arch=$(uname -m) case "$os_arch" in # arm64 strings. These are from https://stackoverflow.com/questions/45125516/possible-values-for-uname-m aarch64|arm64|aarch64_be|armv8b|armv8l) @@ -380,6 +386,15 @@ set_os_arch() ppc64le) os_arch="ppc64le" ;; + powerpc) + if [ "$(uname -s)" = "AIX" ] && command -v bootinfo > /dev/null && [ "$(bootinfo -y)" = "64" ]; then + os_arch="ppc64" + elif command -v bootinfo > /dev/null; then + error_exit "$LINENO" "Command 'bootinfo' not found, OS likely isn't AIX, but we expect it to be" + else + error_exit "$LINENO" "uname returned arch of 'powerpc', but OS is either not AIX or not 64 bit. 'uname -s': $(uname -s), 'bootinfo -y': $(bootinfo -y)" + fi + ;; # armv6/32bit. These are what raspberry pi can return, which is the main reason we support 32-bit arm arm|armv6l|armv7l) os_arch="arm" @@ -394,7 +409,7 @@ set_os_arch() set_package_type() { # if package_path is set get the file extension otherwise look at what's available on the system - if [ -n "$package_path" ]; then + if [ -n "$package_path" ] && [ "$(uname -s)" != "AIX" ]; then case "$package_path" in *.deb) package_type="deb" @@ -409,10 +424,12 @@ set_package_type() else if command -v dpkg > /dev/null 2>&1; then package_type="deb" + elif command -v mkssys > /dev/null 2>&1; then + package_type="mkssys" elif command -v rpm > /dev/null 2>&1; then package_type="rpm" else - error_exit "$LINENO" "Could not find dpkg or rpm on the system" + error_exit "$LINENO" "Could not find mkssys, dpkg or rpm on the system" fi fi @@ -441,7 +458,7 @@ set_download_urls() base_url=$DOWNLOAD_BASE fi - collector_download_url="$base_url/v$version/${PACKAGE_NAME}_v${version}_linux_${os_arch}.${package_type}" + collector_download_url="$base_url/v$version/${PACKAGE_NAME}_v${version}_${os}_${os_arch}.${package_type}" else collector_download_url="$url" fi @@ -563,25 +580,35 @@ os_check() Linux) succeeded ;; + AIX) + succeeded + ;; *) failed error_exit "$LINENO" "The operating system $(fg_yellow "$os_type") is not supported by this script." ;; esac + + # Create lowercase os variable + os=$(echo "$os_type" | tr '[:upper:]' '[:lower:]') } # This will check if the system architecture is supported. os_arch_check() { info "Checking for valid operating system architecture..." - arch=$(uname -m) - case "$arch" in - x86_64|aarch64|ppc64|ppc64le|arm64|aarch64_be|armv8b|armv8l|arm|armv6l|armv7l) + if [ "$(uname -s)" = "AIX" ]; then + os_arch=$(uname -p) + else + os_arch=$(uname -m) + fi + case "$os_arch" in + x86_64|aarch64|powerpc|ppc64|ppc64le|arm64|aarch64_be|armv8b|armv8l|arm|armv6l|armv7l) succeeded ;; *) failed - error_exit "$LINENO" "The operating system architecture $(fg_yellow "$arch") is not supported by this script." + error_exit "$LINENO" "The operating system architecture $(fg_yellow "$os_arch") is not supported by this script." ;; esac } @@ -611,17 +638,32 @@ dependencies_check() succeeded } -# This will check to ensure either dpkg or rpm is installedon the system +# This will check to ensure either dpkg or rpm is installed on the system package_type_check() { info "Checking for package manager..." if command -v dpkg > /dev/null 2>&1; then succeeded + # Check ALL of the AIX commands needed + elif command -v mkssys > /dev/null 2>&1 \ + && command -v mkgroup > /dev/null 2>&1 \ + && command -v useradd > /dev/null 2>&1 \ + && command -v startsrc > /dev/null 2>&1 \ + && command -v stopsrc > /dev/null 2>&1 \ + && command -v lssrc > /dev/null 2>&1 \ + && command -v mkitab > /dev/null 2>&1 \ + && command -v rmitab > /dev/null 2>&1 \ + && command -v lsitab > /dev/null 2>&1; then + succeeded elif command -v rpm > /dev/null 2>&1; then succeeded else failed - error_exit "$LINENO" "Could not find dpkg or rpm on the system" + if [ "$(uname -s)" = "AIX" ]; then + error_exit "$LINENO" "Could not find AIX installation tools on the system" + else + error_exit "$LINENO" "Could not find dpkg or rpm on the system" + fi fi } @@ -630,7 +672,7 @@ latest_version() { curl -sSL -H"Accept: application/vnd.github.v3+json" https://api.github.com/repos/observIQ/observiq-otel-collector/releases/latest | \ grep "\"tag_name\"" | \ - sed -r 's/ *"tag_name": "v([0-9]+\.[0-9]+\.[0-9+])",/\1/' + cut -d: -f2 | tr -d '"' | tr -d ' ' | tr -d ',' | tr -d 'v' } # This will install the package by downloading the archived agent, @@ -663,10 +705,11 @@ install_package() # if target install directory doesn't exist and we're using dpkg ensure a clean state # by checking for the package and running purge if it exists. if [ ! -d "/opt/observiq-otel-collector" ] && [ "$package_type" = "deb" ]; then + info "Running dpkg --purge to ensure a clean state" dpkg -s "observiq-otel-collector" > /dev/null 2>&1 && dpkg --purge "observiq-otel-collector" > /dev/null 2>&1 fi - unpack_package || error_exit "$LINENO" "Failed to extract package" + install_package_file || error_exit "$LINENO" "Failed to extract package" succeeded # If an endpoint was specified, we need to write the manager.yaml @@ -689,7 +732,7 @@ install_package() systemctl enable --now observiq-otel-collector > /dev/null 2>&1 || error_exit "$LINENO" "Failed to enable service" succeeded fi - else + elif [ "$SVC_PRE" = "service" ]; then case "$(service observiq-otel-collector status)" in *running*) # The service is running. @@ -705,18 +748,90 @@ install_package() succeeded ;; esac + elif [ "$SVC_PRE" = "mkssys" ]; then + case "$(lssrc -s observiq-otel-collector | grep collector)" in + *active*) + # The service is running. + # We'll want to restart. + info "Restarting service..." + # AIX does not support service "restart", so stop and start instead + stopsrc -s observiq-otel-collector + # Start the service with the proper environment variables + startsrc -s observiq-otel-collector -a start -e "$(cat /etc/observiq-otel-collector.aix.env)" + succeeded + ;; + *inoperative*) + info "Starting service..." + # Start the service with the proper environment variables + startsrc -s observiq-otel-collector -a start -e "$(cat /etc/observiq-otel-collector.aix.env)" + succeeded + ;; + *) + info "Creating, enabling and starting service..." + # Add the service, removing it if it already exists in order + # to make sure we have the most recent version + if lssrc -s observiq-otel-collector > /dev/null 2>&1; then + rmssys -s observiq-otel-collector + else + mkssys -s observiq-otel-collector -p /opt/observiq-otel-collector/observiq-otel-collector -u "$(id -u observiq-otel-collector)" -S -n15 -f9 -a '--config config.yaml --manager manager.yaml --logging logging.yaml' + fi + + # Install the service to start on boot + # Removing it if it exists, in order to have the most recent version + if lsitab oiqcollector > /dev/null 2>&1; then + rmitab oiqcollector + else + # shellcheck disable=SC2016 + mkitab 'oiqcollector:23456789:respawn:startsrc -s observiq-otel-collector -a start -e "$(cat /etc/observiq-otel-collector.aix.env)"' + fi + + # Start the service with the proper environment variables + startsrc -s observiq-otel-collector -a start -e "$(cat /etc/observiq-otel-collector.aix.env)" + + succeeded + ;; + esac + else + # This is an error state that should never be reached + error_exit "$LINENO" "Found an invalid SVC_PRE value in install_package()" fi success "BindPlane Agent installation complete!" decrease_indent } -unpack_package() +# Install on AIX manually from tar.gz file +install_aix() +{ + # Create the observiq-otel-collector user and group. Group must be first. + mkgroup "$COLLECTOR_USER" > /dev/null 2>&1 + useradd -d /opt/observiq-otel-collector -g "$COLLECTOR_USER" -s "$(which bash)" "$COLLECTOR_USER" > /dev/null 2>&1 + + # Create the install directory + mkdir -p /opt/observiq-otel-collector > /dev/null 2>&1 + + # Extract + zcat "$out_file_path" | tar -Uxvf - -C /opt/observiq-otel-collector > /dev/null 2>&1 + + # Move files to appropriate locations + mv /opt/observiq-otel-collector/opentelemetry-java-contrib-jmx-metrics.jar /opt/ > /dev/null 2>&1 + mv /opt/observiq-otel-collector/install/observiq-otel-collector.aix.env /etc/ > /dev/null 2>&1 + + # Set ownership + chown -R "$COLLECTOR_USER":"$COLLECTOR_USER" /opt/observiq-otel-collector > /dev/null 2>&1 + chown "$COLLECTOR_USER":"$COLLECTOR_USER" /opt/opentelemetry-java-contrib-jmx-metrics.jar > /dev/null 2>&1 + chown "$COLLECTOR_USER":"$COLLECTOR_USER" /etc/observiq-otel-collector.aix.env > /dev/null 2>&1 +} + +install_package_file() { case "$package_type" in deb) dpkg --force-confold -i "$out_file_path" > /dev/null || error_exit "$LINENO" "Failed to unpack package" ;; + mkssys) + install_aix + ;; rpm) rpm -U "$out_file_path" > /dev/null || error_exit "$LINENO" "Failed to unpack package" ;; @@ -782,12 +897,23 @@ display_results() return 0 } +uninstall_aix() +{ + # Remove files + rm -rf /opt/observiq-otel-collector > /dev/null 2>&1 + rm -f /opt/opentelemetry-java-contrib-jmx-metrics.jar > /dev/null 2>&1 + rm -f /etc/observiq-otel-collector.aix.env > /dev/null 2>&1 +} + uninstall_package() { case "$package_type" in deb) dpkg -r "observiq-otel-collector" > /dev/null 2>&1 ;; + mkssys) + uninstall_aix + ;; rpm) rpm -e "observiq-otel-collector" > /dev/null 2>&1 ;; @@ -817,7 +943,7 @@ uninstall() info "Disabling service..." systemctl disable observiq-otel-collector > /dev/null 2>&1 || error_exit "$LINENO" "Failed to disable service" succeeded - else + elif [ "$SVC_PRE" = "service" ]; then info "Stopping service..." service observiq-otel-collector stop succeeded @@ -826,6 +952,31 @@ uninstall() chkconfig observiq-otel-collector on # rm -f /etc/init.d/observiq-otel-collector succeeded + elif [ "$SVC_PRE" = "mkssys" ]; then + # Using case here to bypass =~ missing in the POSIX standard + case "$(lssrc -s observiq-otel-collector | grep collector)" in + *active*) + # The service is running. Stop it before removing it. + info "Stopping service..." + stopsrc -s observiq-otel-collector + ;; + esac + + # Remove the service + info "Disabling service..." + if lsitab oiqcollector > /dev/null 2>&1; then + # Removing start on boot for the service + rmitab oiqcollector + fi + if lssrc -s observiq-otel-collector > /dev/null 2>&1; then + # Removing actual service entry + rmssys -s observiq-otel-collector + fi + + succeeded + else + # This is an error state that should never be reached + error_exit "Found an invalid SVC_PRE value in uninstall()" fi info "Removing any existing manager.yaml..." @@ -840,6 +991,19 @@ uninstall() banner "$(fg_green Uninstallation Complete!)" } +check_aix_name_length() +{ + aix_name_size="$(lsattr -El sys0 -a max_logname | cut -d" " -f 2)" + if [ "$aix_name_size" -lt 24 ]; then + error "$LINENO" "Current system will result in '3004-694 Error adding Name is too long.' when attempting to create group and user" + error "Current max: $aix_name_size" + error "Please raise your limit to 24 characters or greater by issuing these command:" + error " chdev -lsys0 -a max_logname=" + error "and then rebooting the system before attempting to run this script again" + error_exit "Reference: https://www.ibm.com/support/pages/aix-security-change-maximum-length-user-name-group-name-or-password" + fi +} + main() { # We do these checks before we process arguments, because @@ -895,6 +1059,11 @@ main() done fi + # AIX needs a special check + if [ "$os" = "aix" ]; then + check_aix_name_length + fi + interactive_check connection_check setup_installation diff --git a/service/observiq-otel-collector.aix.env b/service/observiq-otel-collector.aix.env new file mode 100644 index 000000000..56b7fcf5c --- /dev/null +++ b/service/observiq-otel-collector.aix.env @@ -0,0 +1 @@ +OIQ_OTEL_COLLECTOR_HOME=/opt/observiq-otel-collector OIQ_OTEL_COLLECTOR_STORAGE=/opt/observiq-otel-collector/storage \ No newline at end of file diff --git a/updater/internal/path/path_unix.go b/updater/internal/path/path_unix.go new file mode 100644 index 000000000..d5e589d3f --- /dev/null +++ b/updater/internal/path/path_unix.go @@ -0,0 +1,27 @@ +// Copyright observIQ, Inc. +// +// 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. + +//go:build aix + +package path + +import "go.uber.org/zap" + +// UNIXInstallDir is the install directory of the collector on BSD Unix. +const UNIXInstallDir = "/opt/observiq-otel-collector" + +// InstallDir returns the filepath to the install directory +func InstallDir(_ *zap.Logger) (string, error) { + return UNIXInstallDir, nil +} diff --git a/updater/internal/service/service_aix.go b/updater/internal/service/service_aix.go new file mode 100644 index 000000000..242b802d0 --- /dev/null +++ b/updater/internal/service/service_aix.go @@ -0,0 +1,140 @@ +// Copyright observIQ, Inc. +// +// 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. + +//go:build aix + +package service + +import ( + "fmt" + "os/exec" + + "go.uber.org/zap" +) + +const aixUnixServiceIdentifier = "oiqcollector" +const aixUnixServiceName = "observiq-otel-collector" + +// Option is an extra option for creating a Service +type Option func(aixUnixSvc *aixUnixService) + +// WithServiceFile returns an option setting the service file to use when updating using the service +func WithServiceFile(svcFilePath string) Option { + return func(aixUnixSvc *aixUnixService) { + // Do nothing + } +} + +// NewService returns an instance of the Service interface for managing the observiq-otel-collector service on the current OS. +func NewService(logger *zap.Logger, installDir string, opts ...Option) Service { + aixUnixSvc := &aixUnixService{ + serviceName: aixUnixServiceName, + serviceIdentifier: aixUnixServiceIdentifier, + installDir: installDir, + logger: logger.Named("aixUnix-service"), + } + + for _, opt := range opts { + opt(aixUnixSvc) + } + + return aixUnixSvc +} + +type aixUnixService struct { + // newServiceFilePath a useless stub to please service_action.go + newServiceFilePath string + // serviceName is the name of the service + serviceName string + // serviceName is the name of the service + serviceIdentifier string + installDir string + logger *zap.Logger +} + +// Start the service +func (l aixUnixService) Start() error { + // startsrc -s observiq-otel-collector -a start -e "$(cat /opt/observiq-otel-collector/observiq-otel-collector.env)" + //#nosec G204 -- serviceName is not determined by user input + cmd := exec.Command("startsrc", "-s", l.serviceName, "-a start -e \"$(cat /opt/observiq-otel-collector/observiq-otel-collector.env)\"") + if err := cmd.Run(); err != nil { + return fmt.Errorf("running service failed: %w", err) + } + return nil +} + +// Stop the service +func (l aixUnixService) Stop() error { + // stopsrc -s observiq-otel-collector + //#nosec G204 -- serviceName is not determined by user input + cmd := exec.Command("stopsrc", "-s", l.serviceName) + if err := cmd.Run(); err != nil { + return fmt.Errorf("running service failed: %w", err) + } + + return nil +} + +// installs the service +func (l aixUnixService) install() error { + // mkssys -s observiq-otel-collector -p /opt/observiq-otel-collector/observiq-otel-collector -u $(id -u observiq-otel-collector) -S -n15 -f9 -a '--config config.yaml --manager manager.yaml --logging logging.yaml' + //#nosec G204 -- serviceName is not determined by user input + cmd := exec.Command("mkssys", "-s", l.serviceName, "-p /opt/observiq-otel-collector/observiq-otel-collector -u $(id -u observiq-otel-collector) -S -n15 -f9 -a '--config config.yaml --manager manager.yaml --logging logging.yaml'") + if err := cmd.Run(); err != nil { + return fmt.Errorf("enabling service file failed: %w", err) + } + // mkitab 'oiqcollector:23456789:respawn:startsrc -s observiq-otel-collector -a start -e "$(cat /opt/observiq-otel-collector/observiq-otel-collector.env)"' + //#nosec G204 -- serviceName is not determined by user input + cmd = exec.Command("mkitab", "'oiqcollector:23456789:respawn:startsrc -s", l.serviceName, "-a start -e \"$(cat /opt/observiq-otel-collector/observiq-otel-collector.env)\"'") + if err := cmd.Run(); err != nil { + return fmt.Errorf("enabling service file failed: %w", err) + } + + return nil +} + +// uninstalls the service +func (l aixUnixService) uninstall() error { + // stopsrc -s observiq-otel-collector + //#nosec G204 -- serviceName is not determined by user input + cmd := exec.Command("stopsrc", "-s", l.serviceName) + if err := cmd.Run(); err != nil { + return fmt.Errorf("running service failed: %w", err) + } + + // rmitab oiqcollector + //#nosec G204 -- serviceIdentifier is not determined by user input + cmd = exec.Command("rmitab", l.serviceIdentifier) + if err := cmd.Run(); err != nil { + return fmt.Errorf("reloading service failed: %w", err) + } + + return nil +} + +func (l aixUnixService) Update() error { + if err := l.uninstall(); err != nil { + return fmt.Errorf("failed to uninstall old service: %w", err) + } + + if err := l.install(); err != nil { + return fmt.Errorf("failed to install new service: %w", err) + } + + return nil +} + +func (l aixUnixService) Backup() error { + return nil +} diff --git a/updater/internal/service/service_aix_test.go b/updater/internal/service/service_aix_test.go new file mode 100644 index 000000000..8ccd3c3a3 --- /dev/null +++ b/updater/internal/service/service_aix_test.go @@ -0,0 +1,284 @@ +// Copyright observIQ, Inc. +// +// 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. + +// an elevated user is needed to run the service tests +//go:build aix && integration_aix + +package service + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/observiq/bindplane-agent/updater/internal/path" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +// NOTE: These tests must run as root in order to pass +func TestaixUnixServiceInstall(t *testing.T) { + t.Run("Test install + uninstall", func(t *testing.T) { + uninstallService(t, installedServicePath, "aix-service") + + l := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := l.install() + require.NoError(t, err) + + //We want to check that the service was actually loaded + requireServiceLoadedStatus(t, true) + + err = l.uninstall() + require.NoError(t, err) + + //Make sure the service is no longer listed + requireServiceLoadedStatus(t, false) + }) + + t.Run("Test stop + start", func(t *testing.T) { + uninstallService(t, installedServicePath, "aix-service") + + l := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := l.install() + require.NoError(t, err) + require.FileExists(t, installedServicePath) + + // We want to check that the service was actually loaded + requireServiceLoadedStatus(t, true) + + err = l.Start() + require.NoError(t, err) + + requireServiceRunningStatus(t, true) + + err = l.Stop() + require.NoError(t, err) + + requireServiceRunningStatus(t, false) + + err = l.uninstall() + require.NoError(t, err) + require.NoFileExists(t, installedServicePath) + + // Make sure the service is no longer listed + requireServiceLoadedStatus(t, false) + }) + + t.Run("Test invalid path for input file", func(t *testing.T) { + installedServicePath := "/usr/lib/systemd/system/aix-service.service" + uninstallService(t, installedServicePath, "aix-service") + + l := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := l.install() + require.ErrorContains(t, err, "failed to open input file") + requireServiceLoadedStatus(t, false) + }) + + t.Run("Test invalid path for output file for install", func(t *testing.T) { + installedServicePath := "/usr/lib/systemd/system/dir-does-not-exist/aix-service.service" + uninstallService(t, installedServicePath, "aix-service") + + l := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := l.install() + require.ErrorContains(t, err, "failed to open output file") + requireServiceLoadedStatus(t, false) + }) + + t.Run("Uninstall fails if not installed", func(t *testing.T) { + installedServicePath := "/usr/lib/systemd/system/aix-service.service" + uninstallService(t, installedServicePath, "aix-service") + + l := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := l.uninstall() + require.ErrorContains(t, err, "failed to disable unit") + requireServiceLoadedStatus(t, false) + }) + + t.Run("Start fails if service not found", func(t *testing.T) { + installedServicePath := "/usr/lib/systemd/system/aix-service.service" + uninstallService(t, installedServicePath, "aix-service") + + l := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := l.Start() + require.ErrorContains(t, err, "running systemctl failed") + }) + + t.Run("Stop fails if service not found", func(t *testing.T) { + installedServicePath := "/usr/lib/systemd/system/aix-service.service" + uninstallService(t, installedServicePath, "aix-service") + + l := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := l.Stop() + require.ErrorContains(t, err, "running systemctl failed") + }) + + t.Run("Backup installed service succeeds", func(t *testing.T) { + installedServicePath := "/usr/lib/systemd/system/aix-service.service" + uninstallService(t, installedServicePath, "aix-service") + + newServiceFile := filepath.Join("testdata", "aix-service.service") + serviceFileContents, err := os.ReadFile(newServiceFile) + require.NoError(t, err) + + installDir := t.TempDir() + require.NoError(t, os.MkdirAll(path.BackupDir(installDir), 0775)) + + d := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err = d.install() + require.NoError(t, err) + require.FileExists(t, installedServicePath) + + // We want to check that the service was actually loaded + requireServiceLoadedStatus(t, true) + + err = d.Backup() + require.NoError(t, err) + require.FileExists(t, path.BackupServiceFile(installDir)) + + backupServiceContents, err := os.ReadFile(path.BackupServiceFile(installDir)) + + require.Equal(t, serviceFileContents, backupServiceContents) + require.NoError(t, d.uninstall()) + }) + + t.Run("Backup installed service fails if not installed", func(t *testing.T) { + installedServicePath := "/usr/lib/systemd/system/aix-service.service" + uninstallService(t, installedServicePath, "aix-service") + + newServiceFile := filepath.Join("testdata", "aix-service.service") + + installDir := t.TempDir() + require.NoError(t, os.MkdirAll(path.BackupDir(installDir), 0775)) + + d := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := d.Backup() + require.ErrorContains(t, err, "failed to copy service file") + }) + + t.Run("Backup installed service fails if output file already exists", func(t *testing.T) { + installedServicePath := "/usr/lib/systemd/system/aix-service.service" + uninstallService(t, installedServicePath, "aix-service") + + newServiceFile := filepath.Join("testdata", "aix-service.service") + + installDir := t.TempDir() + require.NoError(t, os.MkdirAll(path.BackupDir(installDir), 0775)) + + d := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := d.install() + require.NoError(t, err) + require.FileExists(t, installedServicePath) + + // We want to check that the service was actually loaded + requireServiceLoadedStatus(t, true) + + // Write the backup file before creating it; Backup should + // not ever overwrite an existing file + os.WriteFile(path.BackupServiceFile(installDir), []byte("file exists"), 0600) + + err = d.Backup() + require.ErrorContains(t, err, "failed to copy service file") + }) +} + +// uninstallService is a helper that uninstalls the service manually for test setup, in case it is somehow leftover. +func uninstallService(t *testing.T, installedPath, serviceName string) { +} + +const exitCodeServiceNotFound = 1 +const exitCodeServiceInactive = 0 + +func requireServiceLoadedStatus(t *testing.T, loaded bool) { + t.Helper() + + cmd := exec.Command("lssrc", "-s", "aix-service") + err := cmd.Run() + require.Error(t, err, "expected non-zero exit code from 'lssrc -s aix-service'") + + eErr, ok := err.(*exec.ExitError) + if loaded { + // If the service should be loaded, then we expect a 0 exit code, so no error is given + require.Equal(t, exitCodeServiceInactive, eErr.ExitCode(), "unexpected exit code when asserting service is loaded: %d", eErr.ExitCode()) + return + } + + require.True(t, ok, "systemctl status exited with non-ExitError: %s", eErr) + require.Equal(t, exitCodeServiceNotFound, eErr.ExitCode(), "unexpected exit code when asserting service is unloaded: %d", eErr.ExitCode()) +} + +func requireServiceRunningStatus(t *testing.T, running bool) { + cmd := exec.Command("systemctl", "status", "aix-service") + err := cmd.Run() + + if running { + // exit code 0 indicates service is loaded & running + require.NoError(t, err) + return + } + + eErr, ok := err.(*exec.ExitError) + require.True(t, ok, "systemctl status exited with non-ExitError: %s", eErr) + require.Equal(t, exitCodeServiceInactive, eErr.ExitCode(), "unexpected exit code when asserting service is not running: %d", eErr.ExitCode()) +} From 05f08bfe91b995107b06bd8532798a4d8a3b0cf1 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 20 Jun 2024 10:27:47 -0400 Subject: [PATCH 02/13] Add goreleaser yaml file --- .goreleaser.yml | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 6c8e2f256..b45b9df87 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -17,9 +17,10 @@ builds: tags: - bindplane goos: - - windows - - linux + - aix - darwin + - linux + - windows goarch: - amd64 - arm64 @@ -27,6 +28,18 @@ builds: - ppc64 - ppc64le ignore: + - goos: aix + goarch: arm64 + - goos: aix + goarch: arm + - goos: aix + goarch: amd64 + - goos: aix + goarch: ppc64le + - goos: darwin + goarch: ppc64 + - goos: darwin + goarch: ppc64le - goos: windows goarch: arm - goos: windows @@ -35,10 +48,6 @@ builds: goarch: ppc64 - goos: windows goarch: ppc64le - - goos: darwin - goarch: ppc64 - - goos: darwin - goarch: ppc64le ldflags: - -s -w - -X github.com/observiq/bindplane-agent/internal/version.version=v{{ .Version }} @@ -53,9 +62,10 @@ builds: - CGO_ENABLED=0 mod_timestamp: "{{ .CommitTimestamp }}" goos: - - windows - - linux + - aix - darwin + - linux + - windows goarch: - amd64 - arm64 @@ -63,6 +73,18 @@ builds: - ppc64 - ppc64le ignore: + - goos: aix + goarch: arm64 + - goos: aix + goarch: arm + - goos: aix + goarch: amd64 + - goos: aix + goarch: ppc64le + - goos: darwin + goarch: ppc64 + - goos: darwin + goarch: ppc64le - goos: windows goarch: arm - goos: windows @@ -71,10 +93,6 @@ builds: goarch: ppc64 - goos: windows goarch: ppc64le - - goos: darwin - goarch: ppc64 - - goos: darwin - goarch: ppc64le ldflags: - -s -w - -X github.com/observiq/bindplane-agent/updater/internal/version.version=v{{ .Version }} @@ -118,6 +136,9 @@ archives: - src: release_deps/sysconfig/observiq-otel-collector dst: "install/sysconfig" strip_parent: true + - src: release_deps/observiq-otel-collector.aix.env + dst: "install/observiq-otel-collector.aix.env" + strip_parent: true format_overrides: - goos: windows format: zip @@ -206,6 +227,8 @@ nfpms: mode: 0644 owner: root group: root + # This file will be unused on modern systemd Linux + # The postinstall.sh file removes this if it isn't needed - src: service/observiq-otel-collector dst: /etc/init.d/observiq-otel-collector type: config|noreplace @@ -213,6 +236,8 @@ nfpms: mode: 0755 owner: root group: root + # This file will be unused on modern systemd Linux + # The postinstall.sh file removes this if it isn't needed - src: service/sysconfig/observiq-otel-collector dst: /etc/sysconfig/observiq-otel-collector type: config From 2374ebcf2e2f2289009df592b4346be5fb424aa5 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 20 Jun 2024 10:52:34 -0400 Subject: [PATCH 03/13] Explicitly set config to version 2, and change the parameter that breaks it --- .goreleaser.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index b45b9df87..b5ea1abae 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -661,6 +661,7 @@ release: # https://goreleaser.com/customization/changelog/ changelog: + disable: false use: github sort: asc groups: From 10f529cf1fd85bac36a6221effb23979d57d5ecc Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 20 Jun 2024 12:46:32 -0400 Subject: [PATCH 04/13] Add a comment to make it clear why the backup function is emtpy --- updater/internal/service/service_aix.go | 1 + 1 file changed, 1 insertion(+) diff --git a/updater/internal/service/service_aix.go b/updater/internal/service/service_aix.go index 242b802d0..a7837ed2f 100644 --- a/updater/internal/service/service_aix.go +++ b/updater/internal/service/service_aix.go @@ -135,6 +135,7 @@ func (l aixUnixService) Update() error { return nil } +// No files, no backups func (l aixUnixService) Backup() error { return nil } From 45e80884c44f33a1c4da6cfead487f880f659ec3 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 20 Jun 2024 12:53:03 -0400 Subject: [PATCH 05/13] Make uninstall actually work, and clear up a bunch of the comments --- updater/internal/service/service_aix.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/updater/internal/service/service_aix.go b/updater/internal/service/service_aix.go index a7837ed2f..f5d7378af 100644 --- a/updater/internal/service/service_aix.go +++ b/updater/internal/service/service_aix.go @@ -80,7 +80,7 @@ func (l aixUnixService) Stop() error { //#nosec G204 -- serviceName is not determined by user input cmd := exec.Command("stopsrc", "-s", l.serviceName) if err := cmd.Run(); err != nil { - return fmt.Errorf("running service failed: %w", err) + return fmt.Errorf("stopping service failed: %w", err) } return nil @@ -92,7 +92,7 @@ func (l aixUnixService) install() error { //#nosec G204 -- serviceName is not determined by user input cmd := exec.Command("mkssys", "-s", l.serviceName, "-p /opt/observiq-otel-collector/observiq-otel-collector -u $(id -u observiq-otel-collector) -S -n15 -f9 -a '--config config.yaml --manager manager.yaml --logging logging.yaml'") if err := cmd.Run(); err != nil { - return fmt.Errorf("enabling service file failed: %w", err) + return fmt.Errorf("creating service file failed: %w", err) } // mkitab 'oiqcollector:23456789:respawn:startsrc -s observiq-otel-collector -a start -e "$(cat /opt/observiq-otel-collector/observiq-otel-collector.env)"' //#nosec G204 -- serviceName is not determined by user input @@ -106,18 +106,21 @@ func (l aixUnixService) install() error { // uninstalls the service func (l aixUnixService) uninstall() error { - // stopsrc -s observiq-otel-collector - //#nosec G204 -- serviceName is not determined by user input - cmd := exec.Command("stopsrc", "-s", l.serviceName) - if err := cmd.Run(); err != nil { - return fmt.Errorf("running service failed: %w", err) - } + // Stop the service first + l.Stop() // rmitab oiqcollector //#nosec G204 -- serviceIdentifier is not determined by user input cmd = exec.Command("rmitab", l.serviceIdentifier) if err := cmd.Run(); err != nil { - return fmt.Errorf("reloading service failed: %w", err) + return fmt.Errorf("disabling service failed: %w", err) + } + + // rmssys -s observiq-otel-collector + //#nosec G204 -- serviceName is not determined by user input + cmd = exec.Command("rmssys", "-s", l.serviceName) + if err := cmd.Run(); err != nil { + return fmt.Errorf("removing service failed: %w", err) } return nil From 4b6ca60f45fc641f6d7af4ce7fd80e5cb69a0bf8 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 20 Jun 2024 13:40:18 -0400 Subject: [PATCH 06/13] Finalize updater service manager and updater service tests --- updater/internal/service/service_aix.go | 2 +- updater/internal/service/service_aix_test.go | 143 ++----------------- 2 files changed, 13 insertions(+), 132 deletions(-) diff --git a/updater/internal/service/service_aix.go b/updater/internal/service/service_aix.go index f5d7378af..2ab14ce1a 100644 --- a/updater/internal/service/service_aix.go +++ b/updater/internal/service/service_aix.go @@ -111,7 +111,7 @@ func (l aixUnixService) uninstall() error { // rmitab oiqcollector //#nosec G204 -- serviceIdentifier is not determined by user input - cmd = exec.Command("rmitab", l.serviceIdentifier) + cmd := exec.Command("rmitab", l.serviceIdentifier) if err := cmd.Run(); err != nil { return fmt.Errorf("disabling service failed: %w", err) } diff --git a/updater/internal/service/service_aix_test.go b/updater/internal/service/service_aix_test.go index 8ccd3c3a3..4a9675f4c 100644 --- a/updater/internal/service/service_aix_test.go +++ b/updater/internal/service/service_aix_test.go @@ -18,12 +18,9 @@ package service import ( - "os" "os/exec" - "path/filepath" "testing" - "github.com/observiq/bindplane-agent/updater/internal/path" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -31,7 +28,7 @@ import ( // NOTE: These tests must run as root in order to pass func TestaixUnixServiceInstall(t *testing.T) { t.Run("Test install + uninstall", func(t *testing.T) { - uninstallService(t, installedServicePath, "aix-service") + uninstallService(t, "aix-service", "aix_svc") l := &aixUnixService{ serviceName: "aix-service", @@ -52,8 +49,8 @@ func TestaixUnixServiceInstall(t *testing.T) { requireServiceLoadedStatus(t, false) }) - t.Run("Test stop + start", func(t *testing.T) { - uninstallService(t, installedServicePath, "aix-service") + t.Run("Test start + stop", func(t *testing.T) { + uninstallService(t, "aix-service", "aix_svc") l := &aixUnixService{ serviceName: "aix-service", @@ -63,7 +60,6 @@ func TestaixUnixServiceInstall(t *testing.T) { err := l.install() require.NoError(t, err) - require.FileExists(t, installedServicePath) // We want to check that the service was actually loaded requireServiceLoadedStatus(t, true) @@ -80,45 +76,13 @@ func TestaixUnixServiceInstall(t *testing.T) { err = l.uninstall() require.NoError(t, err) - require.NoFileExists(t, installedServicePath) // Make sure the service is no longer listed requireServiceLoadedStatus(t, false) }) - t.Run("Test invalid path for input file", func(t *testing.T) { - installedServicePath := "/usr/lib/systemd/system/aix-service.service" - uninstallService(t, installedServicePath, "aix-service") - - l := &aixUnixService{ - serviceName: "aix-service", - serviceIdentifier: "aix_svc", - logger: zaptest.NewLogger(t), - } - - err := l.install() - require.ErrorContains(t, err, "failed to open input file") - requireServiceLoadedStatus(t, false) - }) - - t.Run("Test invalid path for output file for install", func(t *testing.T) { - installedServicePath := "/usr/lib/systemd/system/dir-does-not-exist/aix-service.service" - uninstallService(t, installedServicePath, "aix-service") - - l := &aixUnixService{ - serviceName: "aix-service", - serviceIdentifier: "aix_svc", - logger: zaptest.NewLogger(t), - } - - err := l.install() - require.ErrorContains(t, err, "failed to open output file") - requireServiceLoadedStatus(t, false) - }) - t.Run("Uninstall fails if not installed", func(t *testing.T) { - installedServicePath := "/usr/lib/systemd/system/aix-service.service" - uninstallService(t, installedServicePath, "aix-service") + uninstallService(t, "aix-service", "aix_svc") l := &aixUnixService{ serviceName: "aix-service", @@ -127,13 +91,12 @@ func TestaixUnixServiceInstall(t *testing.T) { } err := l.uninstall() - require.ErrorContains(t, err, "failed to disable unit") + require.ErrorContains(t, err, "disabling service failed") requireServiceLoadedStatus(t, false) }) t.Run("Start fails if service not found", func(t *testing.T) { - installedServicePath := "/usr/lib/systemd/system/aix-service.service" - uninstallService(t, installedServicePath, "aix-service") + uninstallService(t, "aix-service", "aix_svc") l := &aixUnixService{ serviceName: "aix-service", @@ -142,12 +105,11 @@ func TestaixUnixServiceInstall(t *testing.T) { } err := l.Start() - require.ErrorContains(t, err, "running systemctl failed") + require.ErrorContains(t, err, "running service failed") }) t.Run("Stop fails if service not found", func(t *testing.T) { - installedServicePath := "/usr/lib/systemd/system/aix-service.service" - uninstallService(t, installedServicePath, "aix-service") + uninstallService(t, "aix-service", "aix_svc") l := &aixUnixService{ serviceName: "aix-service", @@ -156,95 +118,14 @@ func TestaixUnixServiceInstall(t *testing.T) { } err := l.Stop() - require.ErrorContains(t, err, "running systemctl failed") - }) - - t.Run("Backup installed service succeeds", func(t *testing.T) { - installedServicePath := "/usr/lib/systemd/system/aix-service.service" - uninstallService(t, installedServicePath, "aix-service") - - newServiceFile := filepath.Join("testdata", "aix-service.service") - serviceFileContents, err := os.ReadFile(newServiceFile) - require.NoError(t, err) - - installDir := t.TempDir() - require.NoError(t, os.MkdirAll(path.BackupDir(installDir), 0775)) - - d := &aixUnixService{ - serviceName: "aix-service", - serviceIdentifier: "aix_svc", - logger: zaptest.NewLogger(t), - } - - err = d.install() - require.NoError(t, err) - require.FileExists(t, installedServicePath) - - // We want to check that the service was actually loaded - requireServiceLoadedStatus(t, true) - - err = d.Backup() - require.NoError(t, err) - require.FileExists(t, path.BackupServiceFile(installDir)) - - backupServiceContents, err := os.ReadFile(path.BackupServiceFile(installDir)) - - require.Equal(t, serviceFileContents, backupServiceContents) - require.NoError(t, d.uninstall()) - }) - - t.Run("Backup installed service fails if not installed", func(t *testing.T) { - installedServicePath := "/usr/lib/systemd/system/aix-service.service" - uninstallService(t, installedServicePath, "aix-service") - - newServiceFile := filepath.Join("testdata", "aix-service.service") - - installDir := t.TempDir() - require.NoError(t, os.MkdirAll(path.BackupDir(installDir), 0775)) - - d := &aixUnixService{ - serviceName: "aix-service", - serviceIdentifier: "aix_svc", - logger: zaptest.NewLogger(t), - } - - err := d.Backup() - require.ErrorContains(t, err, "failed to copy service file") - }) - - t.Run("Backup installed service fails if output file already exists", func(t *testing.T) { - installedServicePath := "/usr/lib/systemd/system/aix-service.service" - uninstallService(t, installedServicePath, "aix-service") - - newServiceFile := filepath.Join("testdata", "aix-service.service") - - installDir := t.TempDir() - require.NoError(t, os.MkdirAll(path.BackupDir(installDir), 0775)) - - d := &aixUnixService{ - serviceName: "aix-service", - serviceIdentifier: "aix_svc", - logger: zaptest.NewLogger(t), - } - - err := d.install() - require.NoError(t, err) - require.FileExists(t, installedServicePath) - - // We want to check that the service was actually loaded - requireServiceLoadedStatus(t, true) - - // Write the backup file before creating it; Backup should - // not ever overwrite an existing file - os.WriteFile(path.BackupServiceFile(installDir), []byte("file exists"), 0600) - - err = d.Backup() - require.ErrorContains(t, err, "failed to copy service file") + require.ErrorContains(t, err, "stopping service failed") }) } // uninstallService is a helper that uninstalls the service manually for test setup, in case it is somehow leftover. -func uninstallService(t *testing.T, installedPath, serviceName string) { +func uninstallService(t *testing.T, serviceName string, serviceIdentifier string) { + cmd := exec.Command("rmitab", serviceIdentifier) + cmd = exec.Command("rmssys", "-s", serviceName) } const exitCodeServiceNotFound = 1 From 5b30216e0008bbcf07e172cc832563e4846b3fec Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 20 Jun 2024 20:32:57 -0400 Subject: [PATCH 07/13] Add Makefile changes --- Makefile | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0dc931106..45eb798b1 100644 --- a/Makefile +++ b/Makefile @@ -44,11 +44,17 @@ updater: build-binaries: agent updater .PHONY: build-all -build-all: build-linux build-darwin build-windows +build-all: build-linux build-unix build-windows .PHONY: build-linux build-linux: build-linux-amd64 build-linux-arm64 build-linux-arm build-linux-ppc64 build-linux-ppc64le +.PHONY: build-unix +build-unix: build-darwin build-aix + +.PHONY: build-aix +build-aix: build-aix-ppc64 + .PHONY: build-darwin build-darwin: build-darwin-amd64 build-darwin-arm64 @@ -75,6 +81,10 @@ build-linux-arm64: build-linux-arm: GOOS=linux GOARCH=arm $(MAKE) build-binaries -j2 +.PHONY: build-aix-ppc64 +build-aix-ppc64: + GOOS=aix GOARCH=ppc64 $(MAKE) build-binaries -j2 + .PHONY: build-darwin-amd64 build-darwin-amd64: GOOS=darwin GOARCH=amd64 $(MAKE) build-binaries -j2 @@ -224,6 +234,7 @@ release-prep: @jq ".files[] | select(.service != null)" windows/wix.json >> release_deps/windows_service.json @cp service/observiq-otel-collector.service release_deps/observiq-otel-collector.service @cp service/observiq-otel-collector release_deps/observiq-otel-collector + @cp service/observiq-otel-collector.aix.env release_deps/observiq-otel-collector.aix.env @cp -r ./service/sysconfig release_deps/ # Build and sign, skip release and ignore dirty git tree From 146f34f38886c632dcd619d53bf7938a65226cc0 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 20 Jun 2024 21:46:13 -0400 Subject: [PATCH 08/13] Fix folder bug for aix env file --- .goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index b5ea1abae..b668524d4 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -137,7 +137,7 @@ archives: dst: "install/sysconfig" strip_parent: true - src: release_deps/observiq-otel-collector.aix.env - dst: "install/observiq-otel-collector.aix.env" + dst: "install" strip_parent: true format_overrides: - goos: windows From 90fb09d85d645002adb1af11d19a86efb16ceeee Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 20 Jun 2024 21:56:50 -0400 Subject: [PATCH 09/13] Make sure storage dir exists for AIX --- scripts/install/install_unix.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/install/install_unix.sh b/scripts/install/install_unix.sh index 216a272db..88a8be4d9 100755 --- a/scripts/install/install_unix.sh +++ b/scripts/install/install_unix.sh @@ -807,8 +807,8 @@ install_aix() mkgroup "$COLLECTOR_USER" > /dev/null 2>&1 useradd -d /opt/observiq-otel-collector -g "$COLLECTOR_USER" -s "$(which bash)" "$COLLECTOR_USER" > /dev/null 2>&1 - # Create the install directory - mkdir -p /opt/observiq-otel-collector > /dev/null 2>&1 + # Create the install & storage directories + mkdir -p /opt/observiq-otel-collector/storage > /dev/null 2>&1 # Extract zcat "$out_file_path" | tar -Uxvf - -C /opt/observiq-otel-collector > /dev/null 2>&1 From b6e9121e4b77e58d02f2e3b56a928793d8fd007c Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 20 Jun 2024 22:56:08 -0400 Subject: [PATCH 10/13] Add protection for the --file option when the file doesn't exist --- scripts/install/install_unix.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/install/install_unix.sh b/scripts/install/install_unix.sh index 88a8be4d9..9999b5b03 100755 --- a/scripts/install/install_unix.sh +++ b/scripts/install/install_unix.sh @@ -320,7 +320,11 @@ setup_installation() set_proxy set_file_name else - out_file_path="$package_path" + if [ ! -f "$package_path" ]; then + error_exit "$LINENO" "--file specified, but '$package_path' does not exist" + else + out_file_path="$package_path" + fi fi set_opamp_endpoint From 1e2a73aebbea1d531fa874d47d3d675f8343c329 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 20 Jun 2024 23:02:17 -0400 Subject: [PATCH 11/13] Add AIX messages, and status for all, to the agent information output of install script --- scripts/install/install_unix.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/install/install_unix.sh b/scripts/install/install_unix.sh index 9999b5b03..6f2134de5 100755 --- a/scripts/install/install_unix.sh +++ b/scripts/install/install_unix.sh @@ -878,10 +878,14 @@ display_results() info "Start Command: $(fg_cyan "sudo systemctl start observiq-otel-collector")$(reset)" info "Stop Command: $(fg_cyan "sudo systemctl stop observiq-otel-collector")$(reset)" info "Status Command: $(fg_cyan "sudo systemctl status observiq-otel-collector")$(reset)" - else + elif [ "$SVC_PRE" = "service" ]; then info "Start Command: $(fg_cyan "sudo service observiq-otel-collector start")$(reset)" info "Stop Command: $(fg_cyan "sudo service observiq-otel-collector stop")$(reset)" info "Status Command: $(fg_cyan "sudo service observiq-otel-collector status")$(reset)" + elif [ "$SVC_PRE" = "mkssys" ]; then + info "Start Command: $(fg_cyan "sudo startsrc -s observiq-otel-collector -a start -e "$(cat /opt/observiq-otel-collector/observiq-otel-collector.env)"")$(reset)" + info "Stop Command: $(fg_cyan "sudo stopsrc -s observiq-otel-collector")$(reset)" + info "Status Command: $(fg_cyan "sudo lssrc -s observiq-otel-collector")$(reset)" fi info "Logs Command: $(fg_cyan "sudo tail -F /opt/observiq-otel-collector/log/collector.log")$(reset)" decrease_indent From 62a63a948bf17e720ca79880e34be50ba35b82b8 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Tue, 25 Jun 2024 10:30:28 -0400 Subject: [PATCH 12/13] Add missing escapes for double quotes inside of double quotes --- scripts/install/install_unix.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install/install_unix.sh b/scripts/install/install_unix.sh index 6f2134de5..dbfa8ff37 100755 --- a/scripts/install/install_unix.sh +++ b/scripts/install/install_unix.sh @@ -883,7 +883,7 @@ display_results() info "Stop Command: $(fg_cyan "sudo service observiq-otel-collector stop")$(reset)" info "Status Command: $(fg_cyan "sudo service observiq-otel-collector status")$(reset)" elif [ "$SVC_PRE" = "mkssys" ]; then - info "Start Command: $(fg_cyan "sudo startsrc -s observiq-otel-collector -a start -e "$(cat /opt/observiq-otel-collector/observiq-otel-collector.env)"")$(reset)" + info "Start Command: $(fg_cyan "sudo startsrc -s observiq-otel-collector -a start -e \"\$(cat /opt/observiq-otel-collector/observiq-otel-collector.env)\"")$(reset)" info "Stop Command: $(fg_cyan "sudo stopsrc -s observiq-otel-collector")$(reset)" info "Status Command: $(fg_cyan "sudo lssrc -s observiq-otel-collector")$(reset)" fi From b4afa1e001b5d35f807e27e8544d20c6eac7d504 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Fri, 28 Jun 2024 09:15:23 -0400 Subject: [PATCH 13/13] Strip receivers down to bare minimums based on what runs on AIX --- factories/receivers_aix.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/factories/receivers_aix.go b/factories/receivers_aix.go index 13862ff72..7c9018e81 100644 --- a/factories/receivers_aix.go +++ b/factories/receivers_aix.go @@ -21,20 +21,9 @@ import ( "github.com/observiq/bindplane-agent/receiver/sapnetweaverreceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/collectdreceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filelogreceiver" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/fluentforwardreceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/influxdbreceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jmxreceiver" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/memcachedreceiver" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mysqlreceiver" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/nginxreceiver" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/opencensusreceiver" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/postgresqlreceiver" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/rabbitmqreceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/saphanareceiver" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/snmpreceiver" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlqueryreceiver" - "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver" "go.opentelemetry.io/collector/receiver" "go.opentelemetry.io/collector/receiver/otlpreceiver" @@ -47,23 +36,12 @@ import ( var defaultReceivers = []receiver.Factory{ collectdreceiver.NewFactory(), filelogreceiver.NewFactory(), - fluentforwardreceiver.NewFactory(), hostmetricsreceiver.NewFactory(), - influxdbreceiver.NewFactory(), jmxreceiver.NewFactory(), - memcachedreceiver.NewFactory(), - mysqlreceiver.NewFactory(), - nginxreceiver.NewFactory(), - opencensusreceiver.NewFactory(), otlpreceiver.NewFactory(), pluginreceiver.NewFactory(), - postgresqlreceiver.NewFactory(), - rabbitmqreceiver.NewFactory(), receivertest.NewNopFactory(), saphanareceiver.NewFactory(), sapnetweaverreceiver.NewFactory(), - snmpreceiver.NewFactory(), - sqlqueryreceiver.NewFactory(), - sqlserverreceiver.NewFactory(), statsdreceiver.NewFactory(), }