diff --git a/.github/workflows/manual_multi_build.yml b/.github/workflows/manual_multi_build.yml index 221a52565..db39192bd 100644 --- a/.github/workflows/manual_multi_build.yml +++ b/.github/workflows/manual_multi_build.yml @@ -97,7 +97,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: "1.22" + go-version: 1.23.6 check-latest: true cache-dependency-path: | **/go.sum diff --git a/.goreleaser.yml b/.goreleaser.yml index 784a3f6de..d6715c525 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -21,52 +21,76 @@ builds: tags: - bindplane goos: - - windows - - linux + - aix - darwin + - linux + - windows goarch: - amd64 - arm64 - ppc64 - ppc64le ignore: - - goos: windows + - goos: aix goarch: arm64 - - goos: windows - goarch: ppc64 - - goos: windows + - goos: aix + goarch: arm + - goos: aix + goarch: amd64 + - goos: aix goarch: ppc64le - goos: darwin goarch: ppc64 - goos: darwin goarch: ppc64le + - goos: windows + goarch: arm64 + - goos: windows + goarch: ppc64 + - goos: windows + goarch: ppc64le prebuilt: path: tmp/collector_{{ .Os }}_{{ .Arch }}{{ .Ext }} - id: supervisor builder: prebuilt binary: opampsupervisor goos: - - windows - - linux + - aix - darwin + - linux + - windows goarch: - amd64 - arm64 - ppc64 - ppc64le ignore: - - goos: windows + - goos: aix + goarch: amd64 + - goos: aix goarch: arm64 - - goos: windows - goarch: ppc64 - - goos: windows + - goos: aix + goarch: arm + - goos: aix goarch: ppc64le - goos: darwin goarch: ppc64 - goos: darwin goarch: ppc64le + - goos: windows + goarch: arm64 + - goos: windows + goarch: ppc64 + - goos: windows + goarch: ppc64le prebuilt: path: release_deps/supervisor_bin/opampsupervisor_{{ .Os }}_{{ .Arch }}{{ .Ext }} + ldflags: + - -s -w + - -X github.com/observiq/bindplane-agent/updater/internal/version.version=v{{ .Version }} + - -X github.com/observiq/bindplane-agent/updater/internal/version.gitHash={{ .FullCommit }} + - -X github.com/observiq/bindplane-agent/updater/internal/version.date={{ .Date }} + no_unique_dist_dir: false # https://goreleaser.com/customization/archive/ archives: @@ -92,6 +116,9 @@ archives: - src: release_deps/bindplane-otel-collector dst: "install" strip_parent: true + - src: release_deps/bindplane-otel-collector.aix.env + dst: "install" + strip_parent: true format_overrides: - goos: windows format: zip @@ -507,6 +534,7 @@ blobs: # https://goreleaser.com/customization/changelog/ changelog: + disable: false use: github sort: asc groups: diff --git a/Makefile b/Makefile index ad098dba8..1f082f976 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,12 @@ collector: CGO_ENABLED=0 builder --config="./manifests/observIQ/manifest.yaml" --ldflags "-s -w -X github.com/observiq/bindplane-otel-collector/internal/version.version=$(VERSION)" mkdir -p $(OUTDIR); cp ./builder/bindplane-otel-collector $(OUTDIR)/collector_$(GOOS)_$(GOARCH)$(EXT) +# Builds the agent for current GOOS/GOARCH pair (aix) +.PHONY: collector-aix +collector-aix: + CGO_ENABLED=0 builder --config="./manifests/observIQ/manifest-aix.yaml" --ldflags "-s -w -X github.com/observiq/bindplane-otel-collector/internal/version.version=$(VERSION)" + mkdir -p $(OUTDIR); cp ./builder/bindplane-otel-collector $(OUTDIR)/collector_$(GOOS)_$(GOARCH)$(EXT) + # Builds a custom distro for the current GOOS/GOARCH pair using the manifest specified # MANIFEST = path to the manifest file for the distro to be built # Usage: make distro MANIFEST="./manifests/custom/my_distro_manifest.yaml" @@ -64,11 +70,17 @@ reset: kill rm -rf agent.log effective.yaml local/supervisor_storage/ builder/ .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-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 @@ -95,6 +107,10 @@ build-linux-arm64: build-linux-arm: GOOS=linux GOARCH=arm $(MAKE) collector +.PHONY: build-aix-ppc64 +build-aix-ppc64: + GOOS=aix GOARCH=ppc64 $(MAKE) collector-aix + .PHONY: build-darwin-amd64 build-darwin-amd64: GOOS=darwin GOARCH=amd64 $(MAKE) collector @@ -237,6 +253,7 @@ release-prep: @cp service/com.bindplane.otel.collector.plist release_deps/com.bindplane.otel.collector.plist @jq ".files[] | select(.service != null)" windows/wix.json >> release_deps/windows_service.json @cp service/bindplane-otel-collector release_deps/bindplane-otel-collector + @cp service/bindplane-otel-collector.aix.env release_deps/bindplane-otel-collector.aix.env # Build and sign, skip release and ignore dirty git tree .PHONY: release-test diff --git a/buildscripts/download-dependencies.sh b/buildscripts/download-dependencies.sh index 28db2c488..5741e7c09 100755 --- a/buildscripts/download-dependencies.sh +++ b/buildscripts/download-dependencies.sh @@ -33,7 +33,7 @@ curl -fL -o "$DOWNLOAD_DIR/opentelemetry-java-contrib-jmx-metrics.jar" \ # download contrib repo and manually build supervisor repos echo "Cloning supervisor repo" SUPERVISOR_REPO="https://github.com/open-telemetry/opentelemetry-collector-contrib.git" -PLATFORMS=("linux/amd64" "linux/arm64" "linux/ppc64" "linux/ppc64le" "darwin/amd64" "darwin/arm64" "windows/amd64") +PLATFORMS=("aix/ppc64" "linux/amd64" "linux/arm64" "linux/ppc64" "linux/ppc64le" "darwin/amd64" "darwin/arm64" "windows/amd64") mkdir "$DOWNLOAD_DIR/supervisor_bin" mkdir "$DOWNLOAD_DIR/opentelemetry-collector-contrib" diff --git a/manifests/observIQ/manifest-aix.yaml b/manifests/observIQ/manifest-aix.yaml new file mode 100644 index 000000000..a76294fdf --- /dev/null +++ b/manifests/observIQ/manifest-aix.yaml @@ -0,0 +1,107 @@ +# This manifest is a combination of the upstream opentelemetry-collector-contrib manifest & components unique to observIQ's distribution. +# https://github.com/open-telemetry/opentelemetry-collector-releases/blob/main/distributions/otelcol-contrib/manifest.yaml +dist: + module: github.com/observiq/bindplane-agent + name: bindplane-otel-collector + description: ObservIQ's custom distro for OpenTelemetry Collector + version: "v2.0.0" + output_path: ./builder +conf_resolver: + default_uri_scheme: "env" +extensions: + - gomod: go.opentelemetry.io/collector/extension/zpagesextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/ackextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/asapauthextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/awsproxy v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/bearertokenauthextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/jaegerencodingextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/otlpencodingextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/zipkinencodingextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/headerssetterextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/httpforwarderextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/jaegerremotesampling v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/oauth2clientauthextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/dockerobserver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/ecsobserver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/ecstaskobserver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/hostobserver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/k8sobserver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/oidcauthextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/opampextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/pprofextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/sigv4authextension v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage/filestorage v0.131.0 + - gomod: github.com/observiq/bindplane-otel-collector/extension/bindplaneextension v1.76.4 + path: "./extension/bindplaneextension" +exporters: + - gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.131.0 + - gomod: go.opentelemetry.io/collector/exporter/nopexporter v0.131.0 + - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.131.0 + - gomod: go.opentelemetry.io/collector/exporter/otlphttpexporter v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter v0.131.0 +processors: + - gomod: go.opentelemetry.io/collector/processor/batchprocessor v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor v0.131.0 + - gomod: github.com/observiq/bindplane-otel-collector/processor/snapshotprocessor v1.76.4 + path: "./processor/snapshotprocessor" + - gomod: github.com/observiq/bindplane-otel-collector/processor/throughputmeasurementprocessor v1.76.4 + path: "./processor/throughputmeasurementprocessor" + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbytraceprocessor v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor v0.131.0 + - gomod: github.com/observiq/bindplane-otel-collector/processor/topologyprocessor v1.76.4 + path: "./processor/topologyprocessor" +receivers: + - gomod: go.opentelemetry.io/collector/receiver/nopreceiver v0.131.0 + - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/collectdreceiver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filelogreceiver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jmxreceiver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/saphanareceiver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sapmreceiver v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver v0.131.0 + - gomod: github.com/observiq/bindplane-otel-collector/receiver/pluginreceiver v1.80.1 + path: "./receiver/pluginreceiver" + - gomod: github.com/observiq/bindplane-otel-collector/receiver/routereceiver v1.80.1 + path: "./receiver/routereceiver" + - gomod: github.com/observiq/bindplane-otel-collector/receiver/telemetrygeneratorreceiver v1.80.1 + path: "./receiver/telemetrygeneratorreceiver" +connectors: + - gomod: go.opentelemetry.io/collector/connector/forwardconnector v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/countconnector v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/exceptionsconnector v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/grafanacloudconnector v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/roundrobinconnector v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/servicegraphconnector v0.131.0 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/spanmetricsconnector v0.131.0 +providers: + - gomod: go.opentelemetry.io/collector/confmap/provider/envprovider v1.37.0 + - gomod: go.opentelemetry.io/collector/confmap/provider/fileprovider v1.37.0 + - gomod: go.opentelemetry.io/collector/confmap/provider/httpprovider v1.37.0 + - gomod: go.opentelemetry.io/collector/confmap/provider/httpsprovider v1.37.0 + - gomod: go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.37.0 +# When adding a replace, add a comment before it to document why it's needed and when it can be removed +replaces: + # See https://github.com/google/gnostic/issues/262 + - github.com/googleapis/gnostic v0.5.6 => github.com/googleapis/gnostic v0.5.5 + # See https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/12322#issuecomment-1185029670 + - github.com/docker/go-connections v0.4.1-0.20210727194412-58542c764a11 => github.com/docker/go-connections v0.4.0 + # see https://github.com/mattn/go-ieproxy/issues/45 + - github.com/mattn/go-ieproxy => github.com/mattn/go-ieproxy v0.0.1 + # see https://github.com/openshift/api/pull/1515 + - github.com/openshift/api => github.com/openshift/api v0.0.0-20230726162818-81f778f3b3ec + # replace for datapointcountprocessor, logcountprocessor, and spancountprocessor + - github.com/observiq/bindplane-otel-collector/receiver/routereceiver => ../receiver/routereceiver + # replace used by bindplaneextension + - github.com/observiq/bindplane-otel-collector/processor/topologyprocessor => ../processor/topologyprocessor + # replace statements for internal imports + - github.com/observiq/bindplane-agent/internal/expr => ../internal/expr + - github.com/observiq/bindplane-agent/internal/counter => ../internal/counter + - github.com/observiq/bindplane-agent/internal/report => ../internal/report + - github.com/observiq/bindplane-agent/internal/testutils => ../internal/testutils + - github.com/observiq/bindplane-agent/internal/rehydration => ../internal/rehydration diff --git a/scripts/install/install_unix.sh b/scripts/install/install_unix.sh index 03140be95..da6f7f80d 100755 --- a/scripts/install/install_unix.sh +++ b/scripts/install/install_unix.sh @@ -24,13 +24,18 @@ 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="bindplane-otel-collector" TMP_DIR=${TMPDIR:-"/tmp"} # Allow this to be overriden by cannonical TMPDIR env var INSTALL_DIR="/opt/bindplane-otel-collector" SUPERVISOR_YML_PATH="$INSTALL_DIR/supervisor.yaml" PREREQS="curl printf $SVC_PRE sed uname cut" +MANAGEMENT_YML_PATH="/opt/bindplane-otel-collector/manager.yaml" +PREREQS="curl printf $SVC_PRE sed uname cut tr tar sudo" SCRIPT_NAME="$0" INDENT_WIDTH=' ' indent="" @@ -44,35 +49,46 @@ error_mode=false # Default Supervisor Config Hash DEFAULT_SUPERVISOR_CFG_HASH="ac4e6001f1b19d371bba6a2797ba0a55d7ca73151ba6908040598ca275c0efca" -# out_file_path is the full path to the downloaded package (e.g. "/tmp/bindplane-otel-collector_linux_amd64.deb") +# Require bash for AIX to create a standardized environment +# Also set up the supervisor timeout values +if [ "$(uname -s)" != "AIX" ]; then + PREREQS="bash $PREREQS" + config_apply_timeout="150" + bootstrap_timeout="15" +else + config_apply_timeout="30" + bootstrap_timeout="5" +fi + +# out_file_path is the full path to the downloaded package (e.g. "/tmp/bindplane-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 @@ -84,8 +100,8 @@ fi # Helper Functions printf() { if [ "$non_interactive" = "false" ] || [ "$error_mode" = "true" ]; then - if command -v sed >/dev/null; then - command printf -- "$@" | sed -r "$sed_ignore s/^/$indent/g" # Ignore sole reset characters if defined + if [ "$(uname -s)" != "AIX" ] && command -v sed >/dev/null; then + command printf -- "$@" | sed -n "$sed_ignore s/^/$indent/g" # Ignore sole reset characters if defined else # Ignore $* suggestion as this breaks the output # shellcheck disable=SC2145 @@ -197,7 +213,7 @@ Usage: Example: '-l http://my.domain.org/bindplane-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/bindplane-otel-collector/binaries' will be used as the base of the download URL. @@ -320,7 +336,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 @@ -334,10 +354,10 @@ setup_installation() { } set_file_name() { - if [ -z "$version" ]; then - package_file_name="${PACKAGE_NAME}_linux_${arch}.${package_type}" + if [ -z "$version" ] ; then + 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" } @@ -369,37 +389,47 @@ set_proxy() { fi } -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) - os_arch="arm64" - ;; - x86_64) - os_arch="amd64" - ;; - # experimental PowerPC arch support for collector - ppc64) - os_arch="ppc64" - ;; - ppc64le) - os_arch="ppc64le" - ;; - # 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" - ;; - *) - error_exit "$LINENO" "Unsupported os arch: $os_arch" - ;; + +set_os_arch() +{ + 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) + os_arch="arm64" + ;; + x86_64) + os_arch="amd64" + ;; + # experimental PowerPC arch support for collector + ppc64) + os_arch="ppc64" + ;; + 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" + ;; + *) + error_exit "$LINENO" "Unsupported os arch: $os_arch" + ;; esac } # Set the package type before install 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" @@ -412,12 +442,14 @@ set_package_type() { ;; esac else - if command -v dpkg >/dev/null 2>&1; then - package_type="deb" - elif command -v rpm >/dev/null 2>&1; then - package_type="rpm" + 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 @@ -445,7 +477,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 @@ -590,28 +622,38 @@ os_check() { info "Checking that the operating system is supported..." os_type=$(uname -s) case "$os_type" in - Linux) - succeeded - ;; - *) - failed - error_exit "$LINENO" "The operating system $(fg_yellow "$os_type") is not supported by this script." - ;; + 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) - succeeded - ;; - *) - failed - error_exit "$LINENO" "The operating system architecture $(fg_yellow "$arch") is not supported by this script." - ;; + 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 "$os_arch") is not supported by this script." + ;; esac } @@ -640,23 +682,40 @@ dependencies_check() { } # This will check to ensure either dpkg or rpm is installed on the system -package_type_check() { +package_type_check() +{ info "Checking for package manager..." - if command -v dpkg >/dev/null 2>&1; then - succeeded - elif command -v rpm >/dev/null 2>&1; then - succeeded + 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" + failed + 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 } # latest_version gets the tag of the latest release, without the v prefix. -latest_version() { - curl -sSL -H"Accept: application/vnd.github.v3+json" https://api.github.com/repos/observIQ/bindplane-otel-collector/releases/latest | - grep "\"tag_name\"" | - sed -r 's/ *"tag_name": "v([0-9]+\.[0-9]+\.[0-9+])",/\1/' +latest_version() +{ + curl -sSL -H"Accept: application/vnd.github.v3+json" https://api.github.com/repos/observIQ/bindplane-otel-collector/releases/latest | \ + grep "\"tag_name\"" | \ + cut -d: -f2 | tr -d '"' | tr -d ' ' | tr -d ',' | tr -d 'v' } # This will install the package by downloading the archived agent, @@ -687,11 +746,12 @@ install_package() { info "Installing 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 "$INSTALL_DIR" ] && [ "$package_type" = "deb" ]; then - dpkg -s "bindplane-otel-collector" >/dev/null 2>&1 && dpkg --purge "bindplane-otel-collector" >/dev/null 2>&1 + if [ ! -d "/opt/bindplane-otel-collector" ] && [ "$package_type" = "deb" ]; then + info "Running dpkg --purge to ensure a clean state" + dpkg -s "bindplane-otel-collector" > /dev/null 2>&1 && dpkg --purge "bindplane-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 create_supervisor_config "$SUPERVISOR_YML_PATH" @@ -709,7 +769,7 @@ install_package() { systemctl enable --now bindplane-otel-collector >/dev/null 2>&1 || error_exit "$LINENO" "Failed to enable service" succeeded fi - else + elif [ "$SVC_PRE" = "service" ]; then case "$(service bindplane-otel-collector status)" in *running*) # The service is running. @@ -725,24 +785,97 @@ install_package() { succeeded ;; esac + elif [ "$SVC_PRE" = "mkssys" ]; then + case "$(lssrc -g bpcollector | 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 -g bpcollector + # Start the service with the proper environment variables + startsrc -g bpcollector -e "$(cat /etc/bindplane-otel-collector.aix.env)" + succeeded + ;; + *inoperative*) + info "Starting service..." + # Start the service with the proper environment variables + startsrc -g bpcollector-e "$(cat /etc/bindplane-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 -g bpcollector > /dev/null 2>&1; then + rmssys -g bpcollector + else + mkssys -s bindplane-otel-collector -G bpcollector -p /opt/bindplane-otel-collector/opampsupervisor -u "$(id -u root)" -S -n15 -f9 -a '--config /opt/bindplane-otel-collector/supervisor.yaml' + fi + + # Install the service to start on boot + # Removing it if it exists, in order to have the most recent version + if lsitab bpcollector > /dev/null 2>&1; then + rmitab bpcollector + else + # shellcheck disable=SC2016 + mkitab 'bpcollector:23456789:once:startsrc -g bpcollector -e "$(cat /etc/bindplane-otel-collector.aix.env)"' + fi + + # Start the service with the proper environment variables + startsrc -g bpcollector -e "$(cat /etc/bindplane-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 "BDOT installation complete!" decrease_indent } -unpack_package() { +# Install on AIX manually from tar.gz file +install_aix() +{ + # Create the bindplane-otel-collector user and group. Group must be first. + mkgroup "$COLLECTOR_USER" > /dev/null 2>&1 + useradd -d /opt/bindplane-otel-collector -g "$COLLECTOR_USER" -s "$(which bash)" "$COLLECTOR_USER" > /dev/null 2>&1 + + # Create the install & storage directories + mkdir -p /opt/bindplane-otel-collector/storage > /dev/null 2>&1 + + # Extract + gunzip -c "$out_file_path" | tar -Uxvf - -C /opt/bindplane-otel-collector > /dev/null 2>&1 + + # Move files to appropriate locations + mv /opt/bindplane-otel-collector/opentelemetry-java-contrib-jmx-metrics.jar /opt/ > /dev/null 2>&1 + mv /opt/bindplane-otel-collector/install/bindplane-otel-collector.aix.env /etc/ > /dev/null 2>&1 + + # Set ownership + chown -R "$COLLECTOR_USER":"$COLLECTOR_USER" /opt/bindplane-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/bindplane-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" - ;; - rpm) - rpm -U "$out_file_path" >/dev/null || error_exit "$LINENO" "Failed to unpack package" - ;; - *) - error "Unrecognized package type" - return 1 - ;; + 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" + ;; + *) + error "Unrecognized package type" + return 1 + ;; esac return 0 } @@ -790,8 +923,8 @@ create_supervisor_config() { command printf ' reports_available_components: true\n' >>"$supervisor_yml_path" command printf 'agent:\n' >>"$supervisor_yml_path" command printf ' executable: "%s"\n' "$INSTALL_DIR/bindplane-otel-collector" >>"$supervisor_yml_path" - command printf ' config_apply_timeout: 30s\n' >>"$supervisor_yml_path" - command printf ' bootstrap_timeout: 5s\n' >>"$supervisor_yml_path" + command printf ' config_apply_timeout: %ss\n' $config_apply_timeout >>"$supervisor_yml_path" + command printf ' bootstrap_timeout: %ss\n' $bootstrap_timeout >>"$supervisor_yml_path" command printf ' args: ["--feature-gates", "service.AllowNoPipelines"]\n' >>"$supervisor_yml_path" if [ -n "$OPAMP_LABELS" ]; then @@ -819,11 +952,15 @@ display_results() { if [ "$SVC_PRE" = "systemctl" ]; then info "Supervisor Start Command: $(fg_cyan "sudo systemctl start bindplane-otel-collector")$(reset)" info "Supervisor Stop Command: $(fg_cyan "sudo systemctl stop bindplane-otel-collector")$(reset)" - info "Status Command: $(fg_cyan "sudo systemctl status bindplane-otel-collector")$(reset)" - else + info "Supervisor Status Command: $(fg_cyan "sudo systemctl status bindplane-otel-collector")$(reset)" + elif [ "$SVC_PRE" = "service" ]; then info "Supervisor Start Command: $(fg_cyan "sudo service bindplane-otel-collector start")$(reset)" info "Supervisor Stop Command: $(fg_cyan "sudo service bindplane-otel-collector stop")$(reset)" - info "Status Command: $(fg_cyan "sudo service bindplane-otel-collector status")$(reset)" + info "Supervisor Status Command: $(fg_cyan "sudo service bindplane-otel-collector status")$(reset)" + elif [ "$SVC_PRE" = "mkssys" ]; then + info "Supervisor Start Command: $(fg_cyan "sudo startsrc -s bindplane-otel-collector -e \"\$(cat /etc/bindplane-otel-collector.aix.env)\"")$(reset)" + info "Supervisor Stop Command: $(fg_cyan "sudo stopsrc -s bindplane-otel-collector")$(reset)" + info "Supervisor Status Command: $(fg_cyan "sudo lssrc -s bindplane-otel-collector")$(reset)" fi info "Uninstall Command: $(fg_cyan "sudo sh -c \"\$(curl -fsSlL https://github.com/observIQ/bindplane-otel-collector/releases/$version/download/install_unix.sh)\" install_unix.sh -r")$(reset)" decrease_indent @@ -839,27 +976,42 @@ display_results() { decrease_indent decrease_indent - banner "$(fg_green Installation Complete!)" - return 0 + banner "$(fg_green Installation Complete!)" + return 0 } -uninstall_package() { +uninstall_aix() +{ + # Remove files + rm -rf /opt/bindplane-otel-collector > /dev/null 2>&1 + rm -f /opt/opentelemetry-java-contrib-jmx-metrics.jar > /dev/null 2>&1 + rm -f /etc/bindplane-otel-collector.aix.env > /dev/null 2>&1 +} + +uninstall_package() +{ case "$package_type" in - deb) - dpkg -r "bindplane-otel-collector" >/dev/null 2>&1 - ;; - rpm) - rpm -e "bindplane-otel-collector" >/dev/null 2>&1 - ;; - *) - error "Unrecognized package type" - return 1 - ;; + deb) + dpkg -r "bindplane-otel-collector" > /dev/null 2>&1 + ;; + mkssys) + uninstall_aix + ;; + rpm) + rpm -e "bindplane-otel-collector" > /dev/null 2>&1 + ;; + *) + error "Unrecognized package type" + return 1 + ;; esac return 0 } -uninstall() { +uninstall() +{ + bindplane_banner + set_package_type banner "Uninstalling BDOT" increase_indent @@ -870,13 +1022,13 @@ uninstall() { if [ "$SVC_PRE" = "systemctl" ]; then info "Stopping service..." - systemctl stop bindplane-otel-collector >/dev/null || error_exit "$LINENO" "Failed to stop service" + systemctl stop bindplane-otel-collector > /dev/null || error_exit "$LINENO" "Failed to stop service" succeeded info "Disabling service..." - systemctl disable bindplane-otel-collector >/dev/null 2>&1 || error_exit "$LINENO" "Failed to disable service" + systemctl disable bindplane-otel-collector > /dev/null 2>&1 || error_exit "$LINENO" "Failed to disable service" succeeded - else + elif [ "$SVC_PRE" = "service" ]; then info "Stopping service..." service bindplane-otel-collector stop succeeded @@ -885,6 +1037,31 @@ uninstall() { chkconfig bindplane-otel-collector on # rm -f /etc/init.d/bindplane-otel-collector succeeded + elif [ "$SVC_PRE" = "mkssys" ]; then + # Using case here to bypass =~ missing in the POSIX standard + case "$(lssrc -s bindplane-otel-collector | grep collector)" in + *active*) + # The service is running. Stop it before removing it. + info "Stopping service..." + stopsrc -s bindplane-otel-collector + ;; + esac + + # Remove the service + info "Disabling service..." + if lsitab bpcollector > /dev/null 2>&1; then + # Removing start on boot for the service + rmitab bpcollector + fi + if lssrc -s bindplane-otel-collector > /dev/null 2>&1; then + # Removing actual service entry + rmssys -s bindplane-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..." @@ -899,6 +1076,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 # some of these options bail early, and we'd like to be sure that those commands @@ -990,6 +1180,12 @@ main() { fi interactive_check + + # AIX needs a special check + if [ "$os" = "aix" ]; then + check_aix_name_length + fi + connection_check setup_installation install_package diff --git a/service/bindplane-otel-collector.aix.env b/service/bindplane-otel-collector.aix.env new file mode 100644 index 000000000..0e4c89cdb --- /dev/null +++ b/service/bindplane-otel-collector.aix.env @@ -0,0 +1 @@ +OIQ_OTEL_COLLECTOR_HOME=/opt/bindplane-otel-collector OIQ_OTEL_COLLECTOR_STORAGE=/opt/bindplane-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..2ab14ce1a --- /dev/null +++ b/updater/internal/service/service_aix.go @@ -0,0 +1,144 @@ +// 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("stopping 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("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 + 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 { + // 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("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 +} + +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 +} + +// No files, no backups +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..4a9675f4c --- /dev/null +++ b/updater/internal/service/service_aix_test.go @@ -0,0 +1,165 @@ +// 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/exec" + "testing" + + "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, "aix-service", "aix_svc") + + 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 start + stop", func(t *testing.T) { + uninstallService(t, "aix-service", "aix_svc") + + 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.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) + + // Make sure the service is no longer listed + requireServiceLoadedStatus(t, false) + }) + + t.Run("Uninstall fails if not installed", func(t *testing.T) { + uninstallService(t, "aix-service", "aix_svc") + + l := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := l.uninstall() + require.ErrorContains(t, err, "disabling service failed") + requireServiceLoadedStatus(t, false) + }) + + t.Run("Start fails if service not found", func(t *testing.T) { + uninstallService(t, "aix-service", "aix_svc") + + l := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := l.Start() + require.ErrorContains(t, err, "running service failed") + }) + + t.Run("Stop fails if service not found", func(t *testing.T) { + uninstallService(t, "aix-service", "aix_svc") + + l := &aixUnixService{ + serviceName: "aix-service", + serviceIdentifier: "aix_svc", + logger: zaptest.NewLogger(t), + } + + err := l.Stop() + 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, serviceName string, serviceIdentifier string) { + cmd := exec.Command("rmitab", serviceIdentifier) + cmd = exec.Command("rmssys", "-s", serviceName) +} + +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()) +}