diff --git a/.github/workflows/_reusable_build_package.yml b/.github/workflows/_reusable_build_package.yml index c7b71034..ba3fff95 100644 --- a/.github/workflows/_reusable_build_package.yml +++ b/.github/workflows/_reusable_build_package.yml @@ -38,6 +38,10 @@ on: required: false type: boolean default: false + packagecloud_go_version: + required: false + type: string + default: "0.2.2" secrets: gh_artifacts_token: required: true @@ -61,6 +65,14 @@ on: required: true packagecloud_token: required: true + aws_access_key_id: + required: true + aws_secret_access_key: + required: true + outputs: + otc_build_number: + description: "The build number of the package" + value: ${{ jobs.build_package.outputs.otc_build_number }} defaults: run: @@ -72,23 +84,12 @@ jobs: name: Build (CMake) if: inputs.build_tool == 'cmake' outputs: + otc_build_number: ${{ steps.get-build-number.outputs.otc_build_number }} package_path: ${{ steps.package.outputs.path }} steps: - name: Checkout uses: actions/checkout@v4 - - name: Download packagecloud-go tool - run: | - baseURL="https://github.com/amdprophet/packagecloud-go/releases/download" - version="0.1.5" - file="packagecloud-go_${version}_linux_amd64.tar.gz" - curl -Lo /tmp/packagecloud-go.tar.gz $baseURL/$version/$file - - - name: Install packagecloud-go tool - run: | - tar -C /tmp -zxf /tmp/packagecloud-go.tar.gz - sudo mv /tmp/packagecloud /usr/local/bin - - name: Workflow URL for sumologic-otel-collector if: ${{ !inputs.use_release_artifacts && inputs.workflow_id != '' }} run: | @@ -97,6 +98,15 @@ jobs: workflow_id="${{ inputs.workflow_id }}" echo "https://github.com/${org}/${repo}/actions/runs/${workflow_id}" + # Only output build number on one target so that it can be read by other + # jobs + - name: Output Build Number + if: inputs.cmake_target == 'otc_linux_amd64_deb' + id: get-build-number + run: | + build_number="${{ inputs.otc_build_number }}" + echo "otc_build_number=${build_number}" >> $GITHUB_OUTPUT + - name: Determine if MacOS package should be signed if: runner.os == 'macOS' env: @@ -228,12 +238,37 @@ jobs: path: ./build/${{ steps.package.outputs.path }} if-no-files-found: error - - name: Publish package to Packagecloud + - name: Publish packages if: runner.os == 'Linux' uses: ./ci/github-actions/make with: target: publish-package packagecloud-token: ${{ secrets.PACKAGECLOUD_TOKEN }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + - name: Publish packages + if: runner.os != 'Linux' + working-directory: build + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} + run: make publish-package + + - name: Wait for Packagecloud packages to be indexed + if: runner.os == 'Linux' + uses: ./ci/github-actions/make + with: + target: wait-for-packagecloud-indexing + packagecloud-token: ${{ secrets.PACKAGECLOUD_TOKEN }} + + - name: Wait for Packagecloud packages to be indexed + if: runner.os == 'Linux' + uses: ./ci/github-actions/make + with: + target: wait-for-packagecloud-indexing + packagecloud-token: ${{ secrets.PACKAGECLOUD_TOKEN }} test_package: runs-on: ${{ inputs.runs_on }} @@ -259,6 +294,7 @@ jobs: name: Build (WiX) ${{ inputs.fips && 'FIPS' || '' }} if: inputs.build_tool == 'wix' env: + OTC_VERSION: ${{ inputs.otc_version }}-${{ inputs.otc_build_number }} PRODUCT_VERSION: ${{ inputs.otc_version }}.${{ inputs.otc_build_number }} steps: - name: Checkout @@ -363,6 +399,17 @@ jobs: path: ${{ steps.build.outputs.package_path }} if-no-files-found: error + - name: Publish packages + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: us-west-2 + PKG_PATH: ${{ steps.build.outputs.package_path }} + PKG_NAME: ${{ steps.build.outputs.package_filename }} + S3_BUCKET: sumologic-osc-ci-builds + run: | + aws.exe s3 cp --debug $PKG_PATH s3://${S3_BUCKET}/${OTC_VERSION}/${PKG_NAME} + test_wixext: name: Test (SumoLogic.wixext) if: inputs.build_tool == 'wix' diff --git a/.github/workflows/build_packages.yml b/.github/workflows/build_packages.yml index a7f313d0..cbcf73d5 100644 --- a/.github/workflows/build_packages.yml +++ b/.github/workflows/build_packages.yml @@ -75,6 +75,10 @@ on: type: boolean required: false default: false + is_latest: + type: boolean + required: false + default: false jobs: determine_workflow: @@ -199,6 +203,8 @@ jobs: microsoft_description: ${{ secrets.MICROSOFT_DESCRIPTION }} gh_ci_token: ${{ secrets.GH_CI_TOKEN }} packagecloud_token: ${{ secrets.PACKAGECLOUD_TOKEN }} + aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} strategy: matrix: @@ -249,6 +255,16 @@ jobs: install-script: name: Store install script runs-on: ubuntu-latest + needs: + - determine_version + - build_packages + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-1 + AWS_S3_BUCKET: sumologic-osc-ci-builds + OTC_VERSION: ${{ needs.determine_version.outputs.otc_version }} + BUILD_NUMBER: ${{ needs.build_packages.outputs.otc_build_number }} steps: - uses: actions/checkout@v4 @@ -266,6 +282,22 @@ jobs: path: ./install-script/install.ps1 if-no-files-found: error + - name: Store install scripts on S3 + run: | + version="${OTC_VERSION}-${BUILD_NUMBER}" + s3_path="${version}/" + aws s3 cp install-script/install.ps1 s3://${AWS_S3_BUCKET}/${s3_path} + aws s3 cp install-script/install.sh s3://${AWS_S3_BUCKET}/${s3_path} + + - name: Create latest_version file + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + run: | + version="${OTC_VERSION}-${BUILD_NUMBER}" + echo "${version}" >> latest_version + aws s3 cp --content-type "text/plain" latest_version s3://${AWS_S3_BUCKET}/ + publish_release: name: Publish Release runs-on: ubuntu-latest @@ -331,3 +363,74 @@ jobs: This release packages [${{ env.OTC_APP_VERSION }}](https://github.com/SumoLogic/sumologic-otel-collector/releases/tag/${{ env.OTC_APP_VERSION }}). The changelog below is for the package itself, rather than the Sumo Logic Distribution for OpenTelemetry Collector. + + test-install-script: + name: Test Install Script + runs-on: ${{ matrix.runs_on }} + timeout-minutes: 60 + needs: + - determine_version + - build_packages + strategy: + fail-fast: false + matrix: + include: + - arch_os: linux_amd64 + runs_on: ubuntu-20.04 + - arch_os: darwin_amd64 + runs_on: macos-latest + - arch_os: windows_amd64 + runs_on: windows-2022 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_CI_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OTC_VERSION: ${{ needs.determine_version.outputs.otc_version }} + OTC_BUILD_NUMBER: ${{ github.run_number }} + PACKAGECLOUD_MASTER_TOKEN: ${{ secrets.PACKAGECLOUD_MASTER_TOKEN }} + PACKAGECLOUD_REPO: ci-builds + steps: + - uses: actions/checkout@v4 + + - name: Check if test related files changed + id: changed-files + uses: tj-actions/changed-files@v44 + with: + files: | + install-script/**/* + .github/** + + - name: Setup go + if: steps.changed-files.outputs.any_changed == 'true' + uses: WillAbides/setup-go-faster@v1 + with: + go-version: stable + + - name: Download macOS package and use it for install.sh + if: ${{ steps.changed-files.outputs.any_changed == 'true' && runner.os == 'macOS' }} + uses: actions/download-artifact@v4 + with: + path: artifacts/ + pattern: otelcol-sumo_*-intel.pkg + + - name: Show packages + if: ${{ steps.changed-files.outputs.any_changed == 'true' && runner.os == 'macOS' }} + run: | + ls -l artifacts/ + ls -l artifacts/**/* + + - name: Set DARWIN_PKG_URL + if: ${{ steps.changed-files.outputs.any_changed == 'true' && runner.os == 'macOS' }} + run: | + fp="$(readlink -f artifacts/otelcol-sumo_*-intel.pkg/otelcol-sumo_*-intel.pkg)" + echo DARWIN_PKG_URL="file://${fp}" >> $GITHUB_ENV + + - name: Run install script tests (*nix) + if: steps.changed-files.outputs.any_changed == 'true' && runner.os != 'Windows' + working-directory: install-script/test + run: make test + + - name: Run install script tests (Windows) + shell: powershell + if: steps.changed-files.outputs.any_changed == 'true' && runner.os == 'Windows' + working-directory: install-script/test + run: make test diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml new file mode 100644 index 00000000..326e05f7 --- /dev/null +++ b/.github/workflows/releases.yml @@ -0,0 +1,168 @@ +name: 'Publish release' + +run-name: > + ${{ format('Publish Release for Workflow: {0}', inputs.workflow_id) }} + +on: + workflow_dispatch: + inputs: + workflow_id: + description: | + Workflow Run ID from this repository to fetch artifacts from for this + release. + required: true + type: string + +defaults: + run: + shell: bash + +jobs: + get-version: + name: Get application version for this revision + runs-on: ubuntu-latest + outputs: + git-sha: ${{ steps.get-version.outputs.git-sha }} + otc-version: ${{ steps.get-version.outputs.otc-version }} + sumo-version: ${{ steps.get-version.outputs.sumo-version }} + binary-version: ${{ steps.get-version.outputs.binary-version }} + version: ${{ steps.get-version.outputs.version }} + steps: + - uses: actions/checkout@v4 + + - name: Outuput Workflow ID + run: echo ::notice title=Workflow ID::${{ inputs.workflow_id }} + + - name: Output Workflow URL + run: | + repo_url="https://github.com/SumoLogic/sumologic-otel-collector-packaging" + url="${repo_url}/actions/runs/${{ inputs.workflow_id }}" + echo ::notice title=Workflow URL::${url} + + - name: Determine Workflow Run ID from workflow + id: get-run-number + run: | + workflow="11673248730" + run_number=$(gh run view "${workflow}" --json number -t '{{.number}}') + echo "run-number=$run_number" >> $GITHUB_OUTPUT + + - name: Output Workflow Run Number + run: | + run_number=${{ steps.get-run-number.outputs.run-number }} + echo ::notice title=Workflow Run Number::${run_number} + + - name: Download otelcol-sumo artifact from workflow + uses: actions/download-artifact@v4 + with: + name: otelcol-sumo-linux_amd64 + path: artifacts/ + merge-multiple: true + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ inputs.workflow_id }} + + - name: Determine version from artifact + id: get-version + run: | + artifact="artifacts/otelcol-sumo-linux_amd64" + chmod +x "${artifact}" + script="ci/get_version_from_binary.sh" + core="$("$script" core "${artifact}")" + sumo="$("$script" sumo "${artifact}")" + run_number=${{ steps.get-run-number.outputs.run-number }} + echo "otc-version=$core" >> $GITHUB_OUTPUT + echo "sumo-version=$sumo" >> $GITHUB_OUTPUT + echo "binary-version=${core}-sumo-${sumo}" >> $GITHUB_OUTPUT + echo "version=${core}-${run_number}" >> $GITHUB_OUTPUT + + - name: Output Binary Version + run: | + binary_version=${{ steps.get-version.outputs.binary-version }} + echo ::notice title=Binary Version::${binary_version} + + - name: Output Package Version + run: | + package_version=${{ steps.get-version.outputs.version }} + echo ::notice title=Package Version::${package_version} + + - name: Determine Git SHA of workflow + id: get-sha + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + workflow=${{ inputs.workflow_id }} + sha="$(gh run view ${workflow} --json headSha -t '{{.headSha}}')" + echo "git-sha=$sha" >> $GITHUB_OUTPUT + + - name: Output Git SHA + run: | + echo ::notice title=Git SHA::${{ steps.get-sha.outputs.git-sha }} + + # Store the install script from the packaging repository as a release + # artifact. + install-script: + name: Store install script + runs-on: ubuntu-latest + needs: + - get-version + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.get-version.outputs.git-sha }} + + - name: Store Linux install script as action artifact + uses: actions/upload-artifact@v4 + with: + name: install.sh + path: ./install-script/install.sh + if-no-files-found: error + + - name: Store Windows install script as action artifact + uses: actions/upload-artifact@v4 + with: + name: install.ps1 + path: ./install-script/install.ps1 + if-no-files-found: error + + create-release: + name: Create Github release + runs-on: ubuntu-20.04 + needs: + - get-version + permissions: + contents: write + steps: + - name: Download all artifacts from workflow + uses: actions/download-artifact@v4 + with: + path: artifacts/ + merge-multiple: true + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ inputs.workflow_id }} + + - uses: ncipollo/release-action@v1 + with: + name: v${{ needs.get-version.outputs.version }} + commit: ${{ needs.get-version.outputs.git-sha }} + tag: v${{ needs.get-version.outputs.version }} + + draft: true + generateReleaseNotes: true + prerelease: false + + allowUpdates: true + omitBodyDuringUpdate: true + omitNameDuringUpdate: true + + artifacts: "artifacts/*/*" + artifactErrorsFailBuild: true + replacesArtifacts: true + + body: | + This release packages + [${{ needs.get-version.outputs.version }}](https://github.com/SumoLogic/sumologic-otel-collector/releases/tag/v${{ needs.get-version.outputs.binary-version }}). + + The changelog below is for the package itself, rather than the Sumo + Logic Distribution for OpenTelemetry Collector. The changelog for + the Sumo Logic Distribution for OpenTelemetry Collector can be found + on the collector's + [release page](https://github.com/SumoLogic/sumologic-otel-collector/releases/tag/v${{ needs.get-version.outputs.binary-version }}). diff --git a/.github/workflows/test-install-script.yml b/.github/workflows/test-install-script.yml deleted file mode 100644 index 15eefe6a..00000000 --- a/.github/workflows/test-install-script.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: 'Test install script' - -defaults: - run: - shell: bash - -on: - pull_request: - -jobs: - test-install-script: - name: Test Install Script - runs-on: ${{ matrix.runs_on }} - strategy: - fail-fast: false - matrix: - include: - - arch_os: linux_amd64 - runs_on: ubuntu-20.04 - - arch_os: darwin_amd64 - runs_on: macos-latest - - arch_os: windows_amd64 - runs_on: windows-2022 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_CI_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - - name: Check if test related files changed - id: changed-files - uses: tj-actions/changed-files@v44 - with: - files: | - install-script/**/* - .github/** - - - name: Setup go - if: steps.changed-files.outputs.any_changed == 'true' - uses: WillAbides/setup-go-faster@v1 - with: - go-version: stable - - - name: Install otelcol-config - run: go install github.com/SumoLogic/sumologic-otel-collector/pkg/tools/otelcol-config@latest - - - name: Run install script tests - if: steps.changed-files.outputs.any_changed == 'true' - working-directory: install-script/test - run: make test diff --git a/.gitignore b/.gitignore index 11530c4c..37e3d589 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ wix/packages msi/wix/packages msi/SumoLogic.wixext/packages install-script/test/sumologic_scripts_tests.test + +# 1Password +.op/ diff --git a/CMakeLists.txt b/CMakeLists.txt index bee55ed6..7dbefff8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.24.1 FATAL_ERROR) # Required and optional programs. Attempts to find required and optional # programs used to build the packages. -find_program(PACKAGECLOUD_PROGRAM packagecloud REQUIRED) +find_program(PACKAGECLOUD_PROGRAM packagecloud) # Set version information include("${CMAKE_SOURCE_DIR}/version.cmake") diff --git a/Dockerfile b/Dockerfile index cc7607cc..baa2e385 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,8 @@ RUN apk add --no-cache \ curl \ bash \ tar \ - gzip + gzip \ + aws-cli COPY docker/install-deps.sh /install-deps.sh diff --git a/Makefile b/Makefile index 5dce5e65..0cccb15a 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,8 @@ publish-package: -v "$(mkfile_dir):/src" \ -v "$(build_dir):/build" \ -e PACKAGECLOUD_TOKEN="$(PACKAGECLOUD_TOKEN)" \ + -e AWS_ACCESS_KEY_ID="$(AWS_ACCESS_KEY_ID)" \ + -e AWS_SECRET_ACCESS_KEY="$(AWS_SECRET_ACCESS_KEY)" otelcol-sumo/cmake \ make publish-package diff --git a/assets/.keep b/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/assets/conf.d/ephemeral.yaml b/assets/conf.d/ephemeral.yaml new file mode 100644 index 00000000..4aa2d995 --- /dev/null +++ b/assets/conf.d/ephemeral.yaml @@ -0,0 +1,3 @@ +extensions: + sumologic: + ephemeral: true diff --git a/assets/productbuild/uninstall.sh b/assets/productbuild/uninstall.sh index c17d1b34..5cce4d9e 100755 --- a/assets/productbuild/uninstall.sh +++ b/assets/productbuild/uninstall.sh @@ -43,19 +43,19 @@ collector_files=( "/etc/otelcol-sumo/sumologic.yaml" "/etc/otelcol-sumo/conf.d" "/etc/otelcol-sumo/conf.d-available" + "/etc/otelcol-sumo/conf.d-available/ephemeral.yaml" + "/etc/otelcol-sumo/conf.d-available/hostmetrics.yaml" "/etc/otelcol-sumo" + "/etc/otelcol-sumo/opamp.d" "/usr/local/bin/otelcol-config" "/usr/local/bin/otelcol-sumo" + "/usr/local/share/otelcol-sumo/otelcol-sumo.sh" + "/usr/local/share/otelcol-sumo" "/var/lib/otelcol-sumo/file_storage" "/var/lib/otelcol-sumo" "/var/log/otelcol-sumo" ) -# A list of files & directories to remove for hostmetrics -hostmetrics_files=( - "/etc/otelcol-sumo/conf.d-available/hostmetrics.yaml" -) - function package_is_registered() { package_id="$1" pkgutil --pkg-info "$package_id" @@ -114,7 +114,6 @@ function uninstall_package() { stop_service "${service_plist_file}" uninstall_package "com.sumologic.otelcol-sumo" "${collector_files[@]}" -uninstall_package "com.sumologic.otelcol-sumo-hostmetrics" "${hostmetrics_files[@]}" # remove the directory that this script belongs to SCRIPT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" diff --git a/assets/services/launchd/com.sumologic.otelcol-sumo.plist b/assets/services/launchd/com.sumologic.otelcol-sumo.plist index 0aaaf732..b1aec424 100644 --- a/assets/services/launchd/com.sumologic.otelcol-sumo.plist +++ b/assets/services/launchd/com.sumologic.otelcol-sumo.plist @@ -6,12 +6,13 @@ otelcol-sumo ProgramArguments - /usr/local/bin/otelcol-sumo - --config - /etc/otelcol-sumo/sumologic.yaml - --config - glob:/etc/otelcol-sumo/conf.d/*.yaml + /usr/local/share/otelcol-sumo/otelcol-sumo.sh + EnvironmentVariables + + SUMOLOGIC_INSTALLATION_TOKEN + + UserName _otelcol-sumo diff --git a/assets/services/systemd/otelcol-sumo.service b/assets/services/systemd/otelcol-sumo.service index cc56b3ba..d435184b 100644 --- a/assets/services/systemd/otelcol-sumo.service +++ b/assets/services/systemd/otelcol-sumo.service @@ -2,7 +2,7 @@ Description=Sumo Logic Distribution for OpenTelemetry Collector [Service] -ExecStart=/usr/local/bin/otelcol-sumo --config /etc/otelcol-sumo/sumologic.yaml --config "glob:/etc/otelcol-sumo/conf.d/*.yaml" +ExecStart=/usr/share/otelcol-sumo/otelcol-sumo.sh User=otelcol-sumo Group=otelcol-sumo MemoryHigh=2000M diff --git a/ci/github-actions/make/action.yml b/ci/github-actions/make/action.yml index 85391fd1..0c348636 100644 --- a/ci/github-actions/make/action.yml +++ b/ci/github-actions/make/action.yml @@ -7,12 +7,20 @@ inputs: packagecloud-token: required: false type: string + aws-access-key-id: + required: false + type: string + aws-secret-access-key: + required: false + type: string runs: using: 'docker' image: '../../../Dockerfile' env: PACKAGECLOUD_TOKEN: ${{ inputs.packagecloud-token }} + AWS_ACCESS_KEY_ID: ${{ inputs.aws-access-key-id }} + AWS_SECRET_ACCESS_KEY: ${{ inputs.aws-secret-access-key }} args: - make - ${{ inputs.target }} diff --git a/ci/verify_installer.sh b/ci/verify_installer.sh index 12a16d94..26a7314f 100755 --- a/ci/verify_installer.sh +++ b/ci/verify_installer.sh @@ -37,6 +37,7 @@ system_files=( "usr" "usr/local" "usr/local/bin" + "usr/local/share" "var" "var/lib" "var/log" @@ -48,23 +49,23 @@ expected_collector_files=( "etc/otelcol-sumo/conf.d" "etc/otelcol-sumo/conf.d/common.yaml" "etc/otelcol-sumo/conf.d-available" + "etc/otelcol-sumo/conf.d-available/ephemeral.yaml" + "etc/otelcol-sumo/conf.d-available/hostmetrics.yaml" "etc/otelcol-sumo/conf.d-available/examples" + "etc/otelcol-sumo/opamp.d" "etc/otelcol-sumo/sumologic.yaml" "Library/Application Support/otelcol-sumo" "Library/Application Support/otelcol-sumo/uninstall.sh" "Library/LaunchDaemons/com.sumologic.otelcol-sumo.plist" "usr/local/bin/otelcol-config" "usr/local/bin/otelcol-sumo" + "usr/local/share/otelcol-sumo" + "usr/local/share/otelcol-sumo/otelcol-sumo.sh" "var/lib/otelcol-sumo" "var/lib/otelcol-sumo/file_storage" "var/log/otelcol-sumo" ) -# a list of files that the hostmetrics package should install -expected_hostmetrics_files=( - "etc/otelcol-sumo/conf.d-available/hostmetrics.yaml" -) - function install_package() { mpkg="$1" mpkg_basename="$2" @@ -184,15 +185,9 @@ while IFS= read -r -d $'\0'; do collector_pkg+=("$REPLY") done < <(find . -name "*-otelcol-sumo.pkg" -type d -print0) -# create an array of hostmetrics packages (only one is expected) -hostmetrics_pkg=() -while IFS= read -r -d $'\0'; do - hostmetrics_pkg+=("$REPLY") -done < <(find . -name "*-otelcol-sumo-hostmetrics.pkg" -type d -print0) - # verify that the expected number of sub-packages were found pkg_count="${#all_pkgs[@]}" -expected_pkg_count=2 +expected_pkg_count=1 if [ "$pkg_count" -ne $expected_pkg_count ]; then echo "error: ${expected_pkg_count} sub-packages were expected but found ${pkg_count}" @@ -205,12 +200,6 @@ if [ "${#collector_pkg[@]}" -gt 1 ]; then exit 1 fi -# only one hostmetrics sub-package should exist -if [ "${#hostmetrics_pkg[@]}" -gt 1 ]; then - echo "error: more than one hostmetrics sub-package was found" - exit 1 -fi - # get a list of files installed by the collector sub-package excluding system # files collector_pkg_name="$(echo "${collector_pkg[0]}" | cut -d/ -f2-)" @@ -243,41 +232,6 @@ for f in "${all_collector_files[@]}"; do collector_files+=("$collector_file") done -cd "$expanded_dir" || exit - -# get a list of files installed by the hostmetrics sub-package excluding both -# system files and collector files -hostmetrics_pkg_name="$(echo "${hostmetrics_pkg[0]}" | cut -d/ -f2-)" -cd "${hostmetrics_pkg_name}/Payload" || exit -all_hostmetrics_files=() -while IFS= read -r -d $'\0'; do - all_hostmetrics_files+=("$REPLY") -done < <(find . ! -name '.' -print0) - -hostmetrics_files=() - -for f in "${all_hostmetrics_files[@]}"; do - hostmetrics_file="$(echo "$f" | cut -d/ -f2-)" - - # shellcheck disable=SC2076 - if [[ " ${system_files[*]} " =~ " ${hostmetrics_file} " ]]; then - continue - fi - - # shellcheck disable=SC2076 - if [[ " ${collector_files[*]} " =~ " ${hostmetrics_file} " ]]; then - continue - fi - - # shellcheck disable=SC2076 - if [[ ! " ${expected_collector_files[*]} " =~ " ${collector_file} " ]]; then - echo "error: unexpected file installed by hostmetrics sub-package: ${hostmetrics_file}" - exit 1 - fi - - hostmetrics_files+=("$hostmetrics_file") -done - cd "$exec_dir" || exit install_package "$mpkg" "$mpkg_basename" @@ -287,7 +241,6 @@ echo "########################################################################## echo "Verifying installation: ${mpkg}" echo "################################################################################" verify_installation "com.sumologic.otelcol-sumo" "${expected_collector_files[@]}" -verify_installation "com.sumologic.otelcol-sumo-hostmetrics" "${expected_hostmetrics_files[@]}" echo echo "################################################################################" @@ -305,6 +258,5 @@ echo "########################################################################## echo "Verifying uninstallation: ${mpkg}" echo "################################################################################" verify_uninstallation "com.sumologic.otelcol-sumo" "${expected_collector_files[@]}" -verify_uninstallation "com.sumologic.otelcol-sumo-hostmetrics" "${expected_hostmetrics_files[@]}" echo "Success!" diff --git a/components/otelcol-sumo.cmake b/components/otelcol-sumo.cmake index 147949fe..60d441e8 100644 --- a/components/otelcol-sumo.cmake +++ b/components/otelcol-sumo.cmake @@ -1,29 +1,34 @@ macro(default_otc_linux_install) - create_otc_components() install_otc_config_directory() install_otc_config_fragment_directory() + install_otc_config_fragments_available_directory() install_otc_config_examples() install_otc_user_env_directory() + install_otc_opampd_directory() install_otc_state_directory() install_otc_filestorage_state_directory() install_otc_sumologic_yaml() install_otc_common_yaml() + install_otc_linux_hostmetrics_yaml() + install_otc_ephemeral_yaml() install_otc_token_env() install_otc_binary() install_otc_config_binary() endmacro() macro(default_otc_darwin_install) - create_otc_components() install_otc_config_directory() install_otc_config_fragment_directory() + install_otc_config_fragments_available_directory() install_otc_config_examples() + install_otc_opampd_directory() install_otc_state_directory() install_otc_filestorage_state_directory() install_otc_log_directory() install_otc_sumologic_yaml() install_otc_common_yaml() install_otc_darwin_hostmetrics_yaml() + install_otc_ephemeral_yaml() install_otc_binary() install_otc_config_binary() install_otc_uninstall_script() @@ -40,12 +45,6 @@ macro(create_otc_components) REQUIRED GROUP "otelcol-sumo-group" ) - - cpack_add_component("otelcol-sumo-hostmetrics" - DISPLAY_NAME "Collect Host Metrics" - GROUP "otelcol-sumo-group" - DISABLED - ) endmacro() # e.g. /etc/otelcol-sumo @@ -58,7 +57,7 @@ macro(install_otc_config_directory) DESTINATION "${OTC_CONFIG_DIR}" DIRECTORY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE + GROUP_READ GROUP_WRITE GROUP_EXECUTE WORLD_EXECUTE COMPONENT otelcol-sumo ) @@ -117,8 +116,8 @@ macro(install_otc_config_examples) FILES "${example}" DESTINATION "${OTC_CONFIG_FRAGMENTS_AVAILABLE_DIR}/examples" PERMISSIONS - OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_WRITE GROUP_EXECUTE + OWNER_READ OWNER_WRITE + GROUP_READ GROUP_WRITE COMPONENT otelcol-sumo ) endforeach(example) @@ -139,6 +138,21 @@ macro(install_otc_user_env_directory) ) endmacro() +# e.g. /etc/otelcol-sumo/opamp.d +macro(install_otc_opampd_directory) + require_variables( + "OTC_OPAMPD_DIR" + ) + install( + DIRECTORY + DESTINATION "${OTC_OPAMPD_DIR}" + DIRECTORY_PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_WRITE GROUP_EXECUTE + COMPONENT otelcol-sumo + ) +endmacro() + # e.g. /var/lib/otelcol-sumo macro(install_otc_state_directory) require_variables( @@ -149,7 +163,7 @@ macro(install_otc_state_directory) DESTINATION "${OTC_STATE_DIR}" DIRECTORY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE + GROUP_READ GROUP_WRITE GROUP_EXECUTE COMPONENT otelcol-sumo ) endmacro() @@ -164,7 +178,7 @@ macro(install_otc_filestorage_state_directory) DESTINATION "${OTC_FILESTORAGE_STATE_DIR}" DIRECTORY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE + GROUP_READ GROUP_WRITE GROUP_EXECUTE COMPONENT otelcol-sumo ) endmacro() @@ -254,8 +268,8 @@ macro(install_otc_sumologic_yaml) FILES "${ASSETS_DIR}/sumologic.yaml" DESTINATION "${OTC_CONFIG_DIR}" PERMISSIONS - OWNER_READ - GROUP_READ + OWNER_READ OWNER_WRITE + GROUP_READ GROUP_WRITE RENAME "${OTC_SUMOLOGIC_CONFIG}" COMPONENT otelcol-sumo ) @@ -293,7 +307,7 @@ macro(install_otc_common_yaml) ) endmacro() -# e.g. /etc/otelcol-sumo/conf.d/hostmetrics.yaml +# e.g. /etc/otelcol-sumo/conf.d-available/hostmetrics.yaml macro(install_otc_darwin_hostmetrics_yaml) require_variables( "ASSETS_DIR" @@ -305,13 +319,12 @@ macro(install_otc_darwin_hostmetrics_yaml) RENAME "hostmetrics.yaml" PERMISSIONS OWNER_READ OWNER_WRITE - GROUP_READ - WORLD_READ - COMPONENT otelcol-sumo-hostmetrics + GROUP_READ GROUP_WRITE + COMPONENT otelcol-sumo ) endmacro() -# e.g. /etc/otelcol-sumo/conf.d/hostmetrics.yaml +# e.g. /etc/otelcol-sumo/conf.d-available/hostmetrics.yaml macro(install_otc_linux_hostmetrics_yaml) require_variables( "ASSETS_DIR" @@ -323,9 +336,24 @@ macro(install_otc_linux_hostmetrics_yaml) RENAME "hostmetrics.yaml" PERMISSIONS OWNER_READ OWNER_WRITE - GROUP_READ - WORLD_READ - COMPONENT otelcol-sumo-hostmetrics + GROUP_READ GROUP_WRITE + COMPONENT otelcol-sumo + ) +endmacro() + +# e.g. /etc/otelcol-sumo/conf.d-available/ephemeral.yaml +macro(install_otc_ephemeral_yaml) + require_variables( + "ASSETS_DIR" + "OTC_CONFIG_FRAGMENTS_AVAILABLE_DIR" + ) + install( + FILES "${ASSETS_DIR}/conf.d/ephemeral.yaml" + DESTINATION "${OTC_CONFIG_FRAGMENTS_AVAILABLE_DIR}" + PERMISSIONS + OWNER_READ OWNER_WRITE + GROUP_READ GROUP_WRITE + COMPONENT otelcol-sumo ) endmacro() @@ -333,10 +361,12 @@ endmacro() macro(install_otc_service_systemd) require_variables( "ASSETS_DIR" + "OTC_SYSTEMD_CONFIG" "OTC_SYSTEMD_DIR" ) + install_otc_service_script() install( - FILES "${ASSETS_DIR}/services/systemd/otelcol-sumo.service" + FILES "${ASSETS_DIR}/services/systemd/${OTC_SYSTEMD_CONFIG}" DESTINATION "${OTC_SYSTEMD_DIR}" PERMISSIONS OWNER_READ OWNER_WRITE @@ -350,10 +380,12 @@ endmacro() macro(install_otc_service_launchd) require_variables( "ASSETS_DIR" + "OTC_LAUNCHD_CONFIG" "OTC_LAUNCHD_DIR" ) + install_otc_service_script() install( - FILES "${ASSETS_DIR}/services/launchd/com.sumologic.otelcol-sumo.plist" + FILES "${ASSETS_DIR}/services/launchd/${OTC_LAUNCHD_CONFIG}" DESTINATION "${OTC_LAUNCHD_DIR}" PERMISSIONS OWNER_READ OWNER_WRITE @@ -361,3 +393,21 @@ macro(install_otc_service_launchd) COMPONENT otelcol-sumo ) endmacro() + +# e.g. /usr/share/otelcol-sumo/otelcol-sumo.sh +macro(install_otc_service_script) + require_variables( + "ASSETS_DIR" + "OTC_SERVICE_SCRIPT" + "OTC_SHARE_DIR" + ) + install( + FILES "${ASSETS_DIR}/${OTC_SERVICE_SCRIPT}" + DESTINATION "${OTC_SHARE_DIR}" + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE + COMPONENT otelcol-sumo + ) +endmacro() diff --git a/distributions.cmake b/distributions.cmake index 98758fa5..a517f18c 100644 --- a/distributions.cmake +++ b/distributions.cmake @@ -1,3 +1,6 @@ +# Checks if the Packagecloud distro supports the package architecture and if it +# does it will be added to the list of Packagecloud distributions to upload the +# package to. macro(check_architecture_support) if(NOT ${package_arch} IN_LIST _supported_architectures) message(FATAL_ERROR "${_distro_name} does not support architecture: ${package_arch}") diff --git a/docker/install-deps.sh b/docker/install-deps.sh index 23ff7f82..007958cf 100755 --- a/docker/install-deps.sh +++ b/docker/install-deps.sh @@ -4,7 +4,7 @@ set -euxo pipefail targetarch="$1" -PACKAGECLOUD_GO_VERSION="0.1.5" +PACKAGECLOUD_GO_VERSION="0.2.2" # Convert between Docker CPU architecture names and other names such as Go's # GOARCH. diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 00000000..9ae1dc91 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,122 @@ +# Releasing + +## How to release + +### Check end-to-end tests + +Check if the Sumo internal e2e tests are passing. + +### Determine the Workflow Run ID to release + +We can begin the process of creating a release once QE has given a thumbs up for +a given package version and the [collector release steps][collector_release] +have already been performed. We can determine the Workflow Run ID to use for a +release using the following steps: + +#### Find the package build number + +Each package has a build number and it's included in the package version & +filename. For example, if the package version that QE validates is 0.108.0-1790 +then the build number is 1790. + +#### Find the collector workflow run + +We can find the workflow used to build the packages by using the package build +number. + +The build number corresponds directly to the GitHub Run Number for a packaging +workflow run in GitHub Actions. Unfortunately, there does not currently appear to +be a way to reference a workflow run using the run number. Instead, we can use +one of two methods to find the workflow run: + +#### Option 1 - Use the `gh` cli tool to find the workflow + +```shell +PAGER=""; BUILD_NUMBER="1790"; \ +gh run list -R sumologic/sumologic-otel-collector-packaging -s success \ +-w build_packages.yml -L 200 -b main --json displayTitle,databaseId,number,url \ +-q ".[] | select(.number == ${BUILD_NUMBER})" +``` + +This will output a number of fields, for example: + +```json +{ + "databaseId": 11673248730, + "displayTitle": "Build for Remote Workflow: 11672946742, Version: 0.108.0-sumo-1\n", + "number": 1790, + "url": "https://github.com/SumoLogic/sumologic-otel-collector-packaging/actions/runs/11673248730" +} +``` + +The number in the `databaseId` field is the ID for the workflow run that built +the packages. + +The workflow run can be viewed by visiting the URL in the `url` field. + +#### Option 2 - Search the GitHub website manually + +Manually search for the run number on the +[Build packages workflow][build_workflow] page. Search for the build number +(e.g. 1790) until you find the corresponding workflow. + +![Finding the packaging workflow run][release_0] + +Once you've found the packaging workflow run, click it to navigate to the +details of the workflow run. The Workflow Run ID can be found in the last part +of the URL in the address bar: + +![Finding the packaging workflow ID][release_1] + +### Trigger the release + +Now that we have the Workflow Run ID we can trigger a release. There are two +methods of doing this. + +#### Option 1 - Use the `gh` cli tool to trigger the release + +A release can be triggered by using the following command (be sure to replace +`WORKFLOW_ID` with the Workflow Run ID from the previous step): + +```shell +PAGER=""; WORKFLOW_ID="11673248730"; \ +gh workflow run build_packages.yml \ +-R sumologic/sumologic-otel-collector-packaging -f workflow_id=${WORKFLOW_ID} +``` + +The status of running workflows can be viewed with the `gh run watch` command. +You will have to manually select the correct workflow run. The name of the run +should have a title similiar to `Publish Release for Workflow: x`). Once you +have selected the correct run the screen will periodically update to show the +status of the run's jobs. + +#### Option 2 - Use the GitHub website to trigger the release + +Navigate to the [Publish release][releases_workflow] workflow in GitHub Actions. +Find and click the `Run workflow` button on the right-hand side of the page. +Fill in the Workflow Run ID from the previous step. If the release should be +considered to be the latest version, click the checkbox for `Latest version`. +Click the `Run workflow` button to trigger the release. + +![Triggering a release][release_2] + +### Publish GitHub release + +The GitHub release is created as draft by the +[releases](../.github/workflows/releases.yml) GitHub Action. + +After the release draft is created, go to [GitHub releases](https://github.com/SumoLogic/sumologic-otel-collector-packaging/releases), +edit the release draft and fill in missing information: + +- Specify versions for upstream OT core and contrib releases +- Copy and paste the Changelog entry for this release from [CHANGELOG.md][changelog] + +After verifying that the release text and all links are good, publish the release. + +[build_workflow]: https://github.com/SumoLogic/sumologic-otel-collector-packaging/actions/workflows/build_packages.yml?query=branch%3Amain +[changelog]: https://github.com/SumoLogic/sumologic-otel-collector/blob/main/CHANGELOG.md +[collector_release]: https://github.com/SumoLogic/sumologic-otel-collector/blob/main/docs/release.md +[release_0]: ../images/release_0.png +[release_1]: ../images/release_1.png +[release_1]: ../images/release_2.png +[releases_workflow]: https://github.com/SumoLogic/sumologic-otel-collector-packaging/actions/workflows/releases.yml diff --git a/images/release_0.png b/images/release_0.png new file mode 100644 index 00000000..2847d3ec Binary files /dev/null and b/images/release_0.png differ diff --git a/images/release_1.png b/images/release_1.png new file mode 100644 index 00000000..6e365b97 Binary files /dev/null and b/images/release_1.png differ diff --git a/images/release_2.png b/images/release_2.png new file mode 100644 index 00000000..8961b7f5 Binary files /dev/null and b/images/release_2.png differ diff --git a/install-script/install.ps1 b/install-script/install.ps1 index 92e7c629..75f9d14e 100644 --- a/install-script/install.ps1 +++ b/install-script/install.ps1 @@ -31,11 +31,32 @@ param ( [string] $Api, # The OpAmp Endpoint used to communicate with the OpAmp backend - [string] $OpAmpApi + [string] $OpAmpApi, + + # OverrideArch overrides the architecture detected by this script. This can + # enable installation of x64 packages on an ARM64 system. + [string] $OverrideArch, + + # S3Bucket is used to specify which S3 bucket to download the MSI package + # from. The default value is set to the value of the S3_BUCKET environment + # variable. + [string] $S3Bucket = $env:S3_BUCKET, + + # S3Region is used to specify which S3 region to download the MSI package + # from. The default value is set to the value of the S3_REGION environment + # variable. + [string] $S3Region = $env:S3_REGION ) -$PackageGithubOrg = "SumoLogic" -$PackageGithubRepo = "sumologic-otel-collector-packaging" +if ($S3Bucket -eq "") { + $S3Bucket = "sumologic-osc-ci-builds" +} + +if ($S3Region -eq "") { + $S3Region = "us-west-2" +} + +$S3URI = "https://" + $S3Bucket + ".s3." + $S3Region + ".amazonaws.com" ## # Security tweaks @@ -146,6 +167,11 @@ function Get-OSName } function Get-ArchName { + param ( + [Parameter(Mandatory, Position=0)] + [bool] $AllowUnsupported + ) + Write-Host "Detecting architecture..." [int] $archId = (Get-CimInstance Win32_Processor)[0].Architecture @@ -160,13 +186,26 @@ function Get-ArchName { switch ($arch) { + x86 { $archName = "x86" } x64 { $archName = "x64" } + MIPS { $archName = "MIPS" } + Alpha { $archName = "Alpha" } + PowerPC { $archName = "PowerPC" } + ia64 { $archName = "ia64" } + ARM64 { $archName = "ARM64" } default { Write-Error "Unsupported architecture:`t${arch}" -ErrorAction Stop } } + # Only x64 is supported at the moment + if (!($AllowUnsupported)) { + if ($archName -ne "x64") { + Write-Error "Unsupported architecture:`t${archName}" -ErrorAction Stop + } + } + return $archName } @@ -210,98 +249,15 @@ function Get-InstalledPackageVersion { return $package.Version.Replace("-", ".") } -function Get-Version { +function Get-LatestVersion { param ( - [Parameter(Mandatory, Position=0)] - [ValidateSet("All", "Latest")] - [string] $Command, - [Parameter(Mandatory, Position=1)] [HttpClient] $HttpClient ) - switch ($Command) { - All { - $request = [HttpRequestMessage]::new() - $request.Method = "GET" - $request.RequestURI = "https://api.github.com/repos/" + $PackageGithubOrg + "/" + $PackageGithubRepo + "/releases" - $request.Headers.Add("Accept", "application/vnd.github+json") - $request.Headers.Add("X-GitHub-Api-Version", "2022-11-28") - - $response = $HttpClient.SendAsync($request).GetAwaiter().GetResult() - if (!($response.IsSuccessStatusCode)) { - $statusCode = [int]$response.StatusCode - $reasonPhrase = $response.StatusCode.ToString() - $errMsg = "${statusCode} ${reasonPhrase}" - - if ($response.Content -ne $null) { - $content = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() - $errMsg += ": ${content}" - } - - Write-Error $errMsg -ErrorAction Stop - } - - $content = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() - $releases = $content | ConvertFrom-Json - $filtered = @() - - foreach ($release in $releases) { - # Skip draft releases - if ($release.Draft -eq $true) { - Write-Debug "Skipping draft release: ${release.Name}" - continue - } - - # Skip prereleases - if ($release.Prerelease -eq $true) { - Write-Debug "Skipping prerelease: ${release.Name}" - continue - } - - $filtered += $release.Tag_name.TrimStart("v") - } - - return $filtered - } - - Latest { - $request = [HttpRequestMessage]::new() - $request.Method = "GET" - $request.RequestURI = "https://github.com/" + $PackageGithubOrg + "/" + $PackageGithubRepo + "/releases/latest" - $request.Headers.Add("Accept", "application/json") - - $response = $HttpClient.SendAsync($request).GetAwaiter().GetResult() - if (!($response.IsSuccessStatusCode)) { - $statusCode = [int]$response.StatusCode - $reasonPhrase = $response.StatusCode.ToString() - $errMsg = "${statusCode} ${reasonPhrase}" - - if ($response.Content -ne $null) { - $content = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() - $errMsg += ": ${content}" - } - - Write-Error $errMsg -ErrorAction Stop - } - - $content = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() - $release = $content | ConvertFrom-Json - - return $release.Tag_name.TrimStart("v") - } - } -} - -function Get-Changelog { - param ( - [Parameter(Mandatory, Position=0)] - [HttpClient] $HttpClient - ) - $request = [HttpRequestMessage]::new() $request.Method = "GET" - $request.RequestURI = "https://raw.githubusercontent.com/SumoLogic/sumologic-otel-collector/main/CHANGELOG.md" + $request.RequestURI = $S3URI + "/latest_version" $response = $HttpClient.SendAsync($request).GetAwaiter().GetResult() if (!($response.IsSuccessStatusCode)) { @@ -317,53 +273,14 @@ function Get-Changelog { Write-Error $errMsg -ErrorAction Stop } - return $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() + $content = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() + return $content -replace "`n","" -replace "`r","" } -function Show-BreakingChanges { +function Get-BinaryFromURI { param ( [Parameter(Mandatory, Position=0)] - [string[]] $Versions, - - [Parameter(Mandatory, Position=1)] - [string] $Changelog - ) - - $splitChangelog = $Changelog -Split "\n" - $breakingVersions = @() - - foreach ($version in $Versions) { - # Get lines matching the following and join them into a string: - # * ## - # * ## Breaking - # * breaking changes - $matchingRegex = "^## |^### Breaking|breaking changes" - $matchingLines = ( - $splitChangelog | Select-String -Pattern $matchingRegex - ) -Join "`r`n" - - # Find the section for $version and get the content between it and the - # next version section. - $isBreakingRegex = "(?s)(?<=## \[v${version}\]).*?(?=\r\n## )" - $isBreakingChange = ( - $matchingLines | Select-String -Pattern $isBreakingRegex - ).Matches.Value -ne "" - - if ($isBreakingChange) { - $breakingVersions += $version - } - } - - if ($breakingVersions.Count -gt 0) { - $versionsStr = $breakingVersions -Join ", v" - Write-Host "The following versions contain breaking changes: v${versionsStr}! Please make sure to read the linked Changelog file." - } -} - -function Get-BinaryFromUri { - param ( - [Parameter(Mandatory, Position=0)] - [string] $Uri, + [string] $URI, [Parameter(Mandatory, Position=1)] [string] $Path, @@ -377,10 +294,10 @@ function Get-BinaryFromUri { Remove-Item $Path } - Write-Host "Preparing to download ${Uri}" - $requestUri = [System.Uri]$Uri + Write-Host "Preparing to download ${URI}" + $requestURI = [System.Uri]$URI $optReadHeaders = [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead - $response = $HttpClient.GetAsync($requestUri, $optReadHeaders).GetAwaiter().GetResult() + $response = $HttpClient.GetAsync($requestURI, $optReadHeaders).GetAwaiter().GetResult() $responseMsg = $response.EnsureSuccessStatusCode() $httpStream = $response.Content.ReadAsStreamAsync().GetAwaiter().GetResult() @@ -391,7 +308,7 @@ function Get-BinaryFromUri { ) $copier = $httpStream.CopyToAsync($fileStream) - Write-Host "Downloading ${requestUri}" + Write-Host "Downloading ${requestURI}" $copier.Wait() $fileStream.Close() $httpStream.Close() @@ -409,10 +326,16 @@ try { } $osName = Get-OSName - $archName = Get-ArchName Write-Host "Detected OS type:`t${osName}" + + $archName = Get-ArchName -AllowUnsupported ($OverrideArch -ne "") Write-Host "Detected architecture:`t${archName}" + if ($OverrideArch -ne "") { + $archName = $OverrideArch + Write-Host "Architecture overridden: `t${archName}" + } + $handler = New-Object HttpClientHandler $handler.AllowAutoRedirect = $true @@ -443,47 +366,22 @@ try { Write-Host "Installed app version:`t${installedAppVersionStr}" Write-Host "Installed package version:`t${installedPackageVersionStr}" - # Get versions, but ignore errors as we fallback to other methods later - Write-Host "Getting versions..." - $versions = Get-Version -Command All -HttpClient $httpClient - # Use user's version if set, otherwise get latest version from API (or website) if ($Version -eq "") { - if ($versions.Count -eq 1) { - $Version = $versions - } elseif ($versions.Count -gt 1) { - $Version = $versions[0] - } else { - $Version = Get-Version -Command Latest -HttpClient $httpClient - } + Write-Host "Getting latest version..." + $Version = Get-LatestVersion -HttpClient $httpClient } - # tags in the packaging repository have a dash before the build number, the Windows convention is a stop - $Tag = $Version - $Version = $Version.Replace("-", ".") + # versions have a dash before the build number, the Windows convention is a dot + $msiVersion = $Version.Replace("-", ".") Write-Host "Package version to install:`t${Version}" # Check if otelcol is already in newest version - if ($installedPackageVersion -eq $Version) { - Write-Host "OpenTelemetry collector is already in newest (${Version}) version" - } else { - # add newline before breaking changes and changelog - Write-Host "" - if (($installedVersion -ne "") -And ($versions -ne $null)) { - # Take versions from installed up to the newest - $index = $versions.IndexOf($installedVersion) - if ($index -gt 0) { - $changelog = Get-Changelog $httpClient - Show-BreakingChanges $versions[0..($index-1)] $changelog - } - } + if ($installedPackageVersion -eq $msiVersion) { + Write-Host "OpenTelemetry collector is already in newest (${msiVersion}) version" } - Write-Host "Changelog:`t`thttps://github.com/SumoLogic/sumologic-otel-collector/blob/main/CHANGELOG.md" - # add newline after breaking changes and changelog - Write-Host "" - # Add -fips to the msi filename if necessary $fipsSuffix = "" if ($Fips -eq $true) { @@ -493,11 +391,10 @@ try { # Download MSI $msiLanguage = "en-US" - $msiFileName = "otelcol-sumo_${Version}_${msiLanguage}.${archName}${fipsSuffix}.msi" - $msiUri = "https://github.com/" + $PackageGithubOrg + "/" + $PackageGithubRepo + "/releases/download/" - $msiUri += "v${Tag}/${msiFileName}" + $msiFileName = "otelcol-sumo_${msiVersion}_${msiLanguage}.${archName}${fipsSuffix}.msi" + $msiURI = $S3URI + "/" + $Version + "/" + $msiFileName $msiPath = "${env:TEMP}\${msiFileName}" - Get-BinaryFromUri $msiUri -Path $msiPath -HttpClient $httpClient + Get-BinaryFromURI $msiURI -Path $msiPath -HttpClient $httpClient # Install MSI [string[]] $msiProperties = @() @@ -533,4 +430,6 @@ try { msiexec.exe /i "$msiPath" /passive $msiProperties } catch [HttpRequestException] { Write-Error $_.Exception.InnerException.Message -} \ No newline at end of file +} + +Write-Host "Installation successful" diff --git a/install-script/install.sh b/install-script/install.sh index 433a7440..a80a2294 100755 --- a/install-script/install.sh +++ b/install-script/install.sh @@ -21,10 +21,10 @@ ARG_SHORT_FIPS='f' ARG_LONG_FIPS='fips' ARG_SHORT_YES='y' ARG_LONG_YES='yes' -ARG_SHORT_SKIP_CONFIG='s' -ARG_LONG_SKIP_CONFIG='skip-config' ARG_SHORT_UNINSTALL='u' ARG_LONG_UNINSTALL='uninstall' +ARG_SHORT_UPGRADE='g' +ARG_LONG_UPGRADE='upgrade' ARG_SHORT_PURGE='p' ARG_LONG_PURGE='purge' ARG_SHORT_SKIP_TOKEN='k' @@ -51,22 +51,19 @@ ARG_LONG_EPHEMERAL='ephemeral' ARG_SHORT_TIMEOUT='m' ARG_LONG_TIMEOUT='download-timeout' -PACKAGE_GITHUB_ORG="SumoLogic" -PACKAGE_GITHUB_REPO="sumologic-otel-collector-packaging" - readonly ARG_SHORT_TOKEN ARG_LONG_TOKEN ARG_SHORT_HELP ARG_LONG_HELP ARG_SHORT_API ARG_LONG_API readonly ARG_SHORT_TAG ARG_LONG_TAG ARG_SHORT_VERSION ARG_LONG_VERSION ARG_SHORT_YES ARG_LONG_YES readonly ARG_SHORT_UNINSTALL ARG_LONG_UNINSTALL +readonly ARG_SHORT_UPGRADE ARG_LONG_UPGRADE readonly ARG_SHORT_PURGE ARG_LONG_PURGE ARG_SHORT_DOWNLOAD ARG_LONG_DOWNLOAD readonly ARG_SHORT_CONFIG_BRANCH ARG_LONG_CONFIG_BRANCH ARG_SHORT_BINARY_BRANCH ARG_LONG_CONFIG_BRANCH -readonly ARG_SHORT_BRANCH ARG_LONG_BRANCH ARG_SHORT_SKIP_CONFIG ARG_LONG_SKIP_CONFIG +readonly ARG_SHORT_BRANCH ARG_LONG_BRANCH readonly ARG_SHORT_SKIP_TOKEN ARG_LONG_SKIP_TOKEN ARG_SHORT_FIPS ARG_LONG_FIPS ENV_TOKEN readonly ARG_SHORT_INSTALL_HOSTMETRICS ARG_LONG_INSTALL_HOSTMETRICS readonly ARG_SHORT_REMOTELY_MANAGED ARG_LONG_REMOTELY_MANAGED readonly ARG_SHORT_EPHEMERAL ARG_LONG_EPHEMERAL readonly ARG_SHORT_TIMEOUT ARG_LONG_TIMEOUT readonly DEPRECATED_ARG_LONG_TOKEN DEPRECATED_ENV_TOKEN DEPRECATED_ARG_LONG_SKIP_TOKEN -readonly PACKAGE_GITHUB_ORG PACKAGE_GITHUB_REPO ############################ Variables (see set_defaults function for default values) @@ -89,10 +86,8 @@ CONTINUE=false CONFIG_DIRECTORY="" USER_ENV_DIRECTORY="" UNINSTALL="" +UPGRADE="" SUMO_BINARY_PATH="" -SKIP_TOKEN="" -SKIP_CONFIG=false -CONFIG_PATH="" COMMON_CONFIG_PATH="" PURGE="" DOWNLOAD_ONLY="" @@ -115,6 +110,15 @@ KEEP_DOWNLOADS=false CURL_MAX_TIME=1800 +# NB: the S3 variables are only used on Darwin +S3_BUCKET="${S3_BUCKET:-sumologic-osc-ci-builds}" +S3_REGION="${S3_REGION:-us-west-2}" +S3_URI="https://${S3_BUCKET}.s3.${S3_REGION}.amazonaws.com" + +PACKAGECLOUD_ORG="${PACKAGECLOUD_ORG:-sumologic}" +PACKAGECLOUD_REPO="${PACKAGECLOUD_REPO:-stable}" +PACKAGECLOUD_MASTER_TOKEN="${PACKAGECLOUD_MASTER_TOKEN:-}" + ############################ Functions function usage() { @@ -130,6 +134,7 @@ Supported arguments: -${ARG_SHORT_TAG}, --${ARG_LONG_TAG} Sets tag for collector. This argument can be use multiple times. One per tag. -${ARG_SHORT_DOWNLOAD}, --${ARG_LONG_DOWNLOAD} Download new binary only and skip configuration part. (Mac OS only) + -${ARG_SHORT_UPGRADE}, --${ARG_LONG_UPGRADE} Upgrades the collector using the system package manager. -${ARG_SHORT_UNINSTALL}, --${ARG_LONG_UNINSTALL} Removes Sumo Logic Distribution for OpenTelemetry Collector from the system and disable Systemd service eventually. Use with '--purge' to remove all configurations as well. @@ -138,7 +143,6 @@ Supported arguments: -${ARG_SHORT_API}, --${ARG_LONG_API} API URL, forces the collector to use non-default API -${ARG_SHORT_OPAMP_API}, --${ARG_LONG_OPAMP_API} OpAmp API URL, forces the collector to use non-default OpAmp API - -${ARG_SHORT_SKIP_CONFIG}, --${ARG_LONG_SKIP_CONFIG} Do not create default configuration. -${ARG_SHORT_VERSION}, --${ARG_LONG_VERSION} Version of Sumo Logic Distribution for OpenTelemetry Collector to install, e.g. 0.57.2-sumo-1. By default it gets latest version. -${ARG_SHORT_FIPS}, --${ARG_LONG_FIPS} Install the FIPS 140-2 compliant binary on Linux. @@ -159,10 +163,8 @@ function set_defaults() { DOWNLOAD_CACHE_DIR="/var/cache/otelcol-sumo" # this is in case we want to keep downloaded binaries CONFIG_DIRECTORY="/etc/otelcol-sumo" SUMO_BINARY_PATH="/usr/local/bin/otelcol-sumo" - REMOTE_CONFIG_DIRECTORY="${CONFIG_DIRECTORY}/opamp.d" USER_ENV_DIRECTORY="${CONFIG_DIRECTORY}/env" TOKEN_ENV_FILE="${USER_ENV_DIRECTORY}/token.env" - CONFIG_PATH="${CONFIG_DIRECTORY}/sumologic.yaml" LAUNCHD_CONFIG="/Library/LaunchDaemons/com.sumologic.otelcol-sumo.plist" LAUNCHD_ENV_KEY="EnvironmentVariables" @@ -173,12 +175,6 @@ function set_defaults() { } function parse_options() { - if [[ $# == 0 && -z "${SUMOLOGIC_INSTALLATION_TOKEN}" ]]; then - echo "Installation token has not been provided. Please set the 'SUMOLOGIC_INSTALLATION_TOKEN' environment variable." - usage - exit 2 - fi - # Transform long options to short ones for arg in "$@"; do @@ -206,9 +202,6 @@ function parse_options() { "--${ARG_LONG_YES}") set -- "$@" "-${ARG_SHORT_YES}" ;; - "--${ARG_LONG_SKIP_CONFIG}") - set -- "$@" "-${ARG_SHORT_SKIP_CONFIG}" - ;; "--${ARG_LONG_VERSION}") set -- "$@" "-${ARG_SHORT_VERSION}" ;; @@ -218,10 +211,14 @@ function parse_options() { "--${ARG_LONG_UNINSTALL}") set -- "$@" "-${ARG_SHORT_UNINSTALL}" ;; + "--${ARG_LONG_UPGRADE}") + set -- "$@" "-${ARG_SHORT_UPGRADE}" + ;; "--${ARG_LONG_PURGE}") set -- "$@" "-${ARG_SHORT_PURGE}" ;; "--${ARG_LONG_SKIP_TOKEN}") + echo "--${ARG_LONG_SKIP_TOKEN}" is deprecated and no longer affects the installation. An installation token is required. set -- "$@" "-${ARG_SHORT_SKIP_TOKEN}" ;; "--${DEPRECATED_ARG_LONG_SKIP_TOKEN}") @@ -246,7 +243,7 @@ function parse_options() { "--${ARG_LONG_TIMEOUT}") set -- "$@" "-${ARG_SHORT_TIMEOUT}" ;; - "-${ARG_SHORT_TOKEN}"|"-${ARG_SHORT_HELP}"|"-${ARG_SHORT_API}"|"-${ARG_SHORT_OPAMP_API}"|"-${ARG_SHORT_TAG}"|"-${ARG_SHORT_SKIP_CONFIG}"|"-${ARG_SHORT_VERSION}"|"-${ARG_SHORT_FIPS}"|"-${ARG_SHORT_YES}"|"-${ARG_SHORT_UNINSTALL}"|"-${ARG_SHORT_PURGE}"|"-${ARG_SHORT_SKIP_TOKEN}"|"-${ARG_SHORT_DOWNLOAD}"|"-${ARG_SHORT_CONFIG_BRANCH}"|"-${ARG_SHORT_BINARY_BRANCH}"|"-${ARG_SHORT_BRANCH}"|"-${ARG_SHORT_KEEP_DOWNLOADS}"|"-${ARG_SHORT_TIMEOUT}"|"-${ARG_SHORT_INSTALL_HOSTMETRICS}"|"-${ARG_SHORT_REMOTELY_MANAGED}"|"-${ARG_SHORT_EPHEMERAL}") + "-${ARG_SHORT_TOKEN}"|"-${ARG_SHORT_HELP}"|"-${ARG_SHORT_API}"|"-${ARG_SHORT_OPAMP_API}"|"-${ARG_SHORT_TAG}"|"-${ARG_SHORT_VERSION}"|"-${ARG_SHORT_FIPS}"|"-${ARG_SHORT_YES}"|"-${ARG_SHORT_UNINSTALL}"|"-${ARG_SHORT_UPGRADE}"|"-${ARG_SHORT_PURGE}"|"-${ARG_SHORT_SKIP_TOKEN}"|"-${ARG_SHORT_DOWNLOAD}"|"-${ARG_SHORT_CONFIG_BRANCH}"|"-${ARG_SHORT_BINARY_BRANCH}"|"-${ARG_SHORT_BRANCH}"|"-${ARG_SHORT_KEEP_DOWNLOADS}"|"-${ARG_SHORT_TIMEOUT}"|"-${ARG_SHORT_INSTALL_HOSTMETRICS}"|"-${ARG_SHORT_REMOTELY_MANAGED}"|"-${ARG_SHORT_EPHEMERAL}") set -- "$@" "${arg}" ;; "--${ARG_LONG_INSTALL_HOSTMETRICS}") @@ -270,7 +267,7 @@ function parse_options() { while true; do set +e - getopts "${ARG_SHORT_HELP}${ARG_SHORT_TOKEN}:${ARG_SHORT_API}:${ARG_SHORT_OPAMP_API}:${ARG_SHORT_TAG}:${ARG_SHORT_VERSION}:${ARG_SHORT_FIPS}${ARG_SHORT_YES}${ARG_SHORT_UNINSTALL}${ARG_SHORT_PURGE}${ARG_SHORT_SKIP_TOKEN}${ARG_SHORT_SKIP_CONFIG}${ARG_SHORT_DOWNLOAD}${ARG_SHORT_KEEP_DOWNLOADS}${ARG_SHORT_CONFIG_BRANCH}:${ARG_SHORT_BINARY_BRANCH}:${ARG_SHORT_BRANCH}:${ARG_SHORT_EPHEMERAL}${ARG_SHORT_REMOTELY_MANAGED}${ARG_SHORT_INSTALL_HOSTMETRICS}${ARG_SHORT_TIMEOUT}:" opt + getopts "${ARG_SHORT_HELP}${ARG_SHORT_TOKEN}:${ARG_SHORT_API}:${ARG_SHORT_OPAMP_API}:${ARG_SHORT_TAG}:${ARG_SHORT_VERSION}:${ARG_SHORT_FIPS}${ARG_SHORT_YES}${ARG_SHORT_UPGRADE}${ARG_SHORT_UNINSTALL}${ARG_SHORT_PURGE}${ARG_SHORT_SKIP_TOKEN}${ARG_SHORT_DOWNLOAD}${ARG_SHORT_KEEP_DOWNLOADS}${ARG_SHORT_CONFIG_BRANCH}:${ARG_SHORT_BINARY_BRANCH}:${ARG_SHORT_BRANCH}:${ARG_SHORT_EPHEMERAL}${ARG_SHORT_REMOTELY_MANAGED}${ARG_SHORT_INSTALL_HOSTMETRICS}${ARG_SHORT_TIMEOUT}:" opt set -e # Invalid argument catched, print and exit @@ -286,13 +283,12 @@ function parse_options() { "${ARG_SHORT_TOKEN}") SUMOLOGIC_INSTALLATION_TOKEN="${OPTARG}" ;; "${ARG_SHORT_API}") API_BASE_URL="${OPTARG}" ;; "${ARG_SHORT_OPAMP_API}") OPAMP_API_URL="${OPTARG}" ;; - "${ARG_SHORT_SKIP_CONFIG}") SKIP_CONFIG=true ;; "${ARG_SHORT_VERSION}") VERSION="${OPTARG}" ;; "${ARG_SHORT_FIPS}") FIPS=true ;; "${ARG_SHORT_YES}") CONTINUE=true ;; "${ARG_SHORT_UNINSTALL}") UNINSTALL=true ;; + "${ARG_SHORT_UPGRADE}") UPGRADE=true ;; "${ARG_SHORT_PURGE}") PURGE=true ;; - "${ARG_SHORT_SKIP_TOKEN}") SKIP_TOKEN=true ;; "${ARG_SHORT_DOWNLOAD}") DOWNLOAD_ONLY=true ;; "${ARG_SHORT_CONFIG_BRANCH}") CONFIG_BRANCH="${OPTARG}" ;; "${ARG_SHORT_BINARY_BRANCH}") BINARY_BRANCH="${OPTARG}" ;; @@ -318,11 +314,6 @@ function parse_options() { done } -# Get github rate limit -function github_rate_limit() { - curl --retry 5 --connect-timeout 5 --max-time 30 --retry-delay 0 --retry-max-time 150 -X GET https://api.github.com/rate_limit -v 2>&1 | grep x-ratelimit-remaining | grep -oE "[0-9]+" -} - # Ensure TMPDIR is set to a directory where we can safely store temporary files function set_tmpdir() { # generate a new tmpdir using mktemp @@ -339,7 +330,7 @@ function check_dependencies() { error=1 fi - REQUIRED_COMMANDS=(echo sed curl head grep sort mv chmod getopts hostname touch xargs) + REQUIRED_COMMANDS=(echo sed curl head grep sort mv getopts hostname touch xargs) if [[ -n "${BINARY_BRANCH}" ]]; then # unzip is only necessary for downloading from GHA artifacts REQUIRED_COMMANDS+=(unzip) fi @@ -356,105 +347,11 @@ function check_dependencies() { fi } -function get_latest_package_version() { - local versions - readonly versions="${1}" - - # get latest version directly from website if there is no versions from api - if [[ -z "${versions}" ]]; then - curl --retry 5 --connect-timeout 5 --max-time 30 --retry-delay 0 \ - --retry-max-time 150 -s \ - "https://github.com/${PACKAGE_GITHUB_ORG}/${PACKAGE_GITHUB_REPO}/releases" \ - | grep -oE '/'${PACKAGE_GITHUB_ORG}'/'${PACKAGE_GITHUB_REPO}'/releases/tag/(.*)"' \ - | head -n 1 \ - | sed 's%/'${PACKAGE_GITHUB_ORG}'/'${PACKAGE_GITHUB_REPO}'/releases/tag/v\([^"]*\)".*%\1%g' - else - # sed 's/ /\n/g' converts spaces to new lines - echo "${versions}" | sed 's/ /\n/g' | head -n 1 - fi -} - -function get_latest_version() { - local versions - readonly versions="${1}" - - # get latest version directly from website if there is no versions from api - if [[ -z "${versions}" ]]; then - curl --retry 5 --connect-timeout 5 --max-time 30 --retry-delay 5 --retry-max-time 150 -s https://github.com/SumoLogic/sumologic-otel-collector/releases \ - | grep -Eo '/SumoLogic/sumologic-otel-collector/releases/tag/v[0-9]+\.[0-9]+\.[0-9]+-sumo-[0-9]+[^-]' \ - | head -n 1 | sed 's%/SumoLogic/sumologic-otel-collector/releases/tag/v\([^"]*\)".*%\1%g' - else - # sed 's/ /\n/g' converts spaces to new lines - echo "${versions}" | sed 's/ /\n/g' | head -n 1 - fi -} - -# Get available versions of otelcol-sumo -# skip prerelease and draft releases -# sort it from last to first -# remove v from beginning of version -function get_versions() { - # returns empty in case we exceeded github rate limit - if [[ "$(github_rate_limit)" == "0" ]]; then - return - fi - - curl \ - --connect-timeout 5 \ - --max-time 30 \ - --retry 5 \ - --retry-delay 0 \ - --retry-max-time 150 \ - -sH "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/SumoLogic/sumologic-otel-collector/releases \ - | grep -E '(tag_name|"(draft|prerelease)")' \ - | sed 'N;N;s/.*true.*//' \ - | grep -o 'v.*"' \ - | sort -rV \ - | sed 's/^v//;s/"$//' -} - -function get_package_versions() { - # returns empty in case we exceeded github rate limit. This can happen if we are running this script too many times in a short period. - if [[ "$(github_rate_limit)" == "0" ]]; then - return - fi - - curl \ - --connect-timeout 5 \ - --max-time 30 \ - --retry 5 \ - --retry-delay 0 \ - --retry-max-time 150 \ - -sH "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/${PACKAGE_GITHUB_ORG}/${PACKAGE_GITHUB_REPO}/releases \ - | grep -E '(tag_name|"(draft|prerelease)")' \ - | sed 'N;N;s/.*true.*//' \ - | grep -o 'v.*"' \ - | sort -rV \ - | sed 's/^v//;s/"$//' -} - -# Get versions from provided one to the latest -get_versions_from() { - local versions - readonly versions="${1}" - - local from - readonly from="${2}" - - # Return if there is no installed version - if [[ "${from}" == "" ]]; then - return 0 - fi - - local line - readonly line="$(( $(echo "${versions}" | sed 's/ /\n/g' | grep -n "${from}$" | sed 's/:.*//g') - 1 ))" - - if [[ "${line}" -gt "0" ]]; then - echo "${versions}" | sed 's/ /\n/g' | head -n "${line}" | sort - fi - return 0 +# NB: this function is only for Darwin +function get_latest_s3_package_version() { + curl --retry 5 --connect-timeout 5 --max-time 30 --retry-delay 0 \ + --retry-max-time 150 -s \ + "${S3_URI}/latest_version" | tr -d '\n' } # Get OS type (linux or darwin) @@ -515,15 +412,6 @@ function verify_installation() { echo -e "Installation succeded:\t$(${otel_command} --version)" } -# Get installed version of otelcol-sumo -function get_installed_version() { - if [[ -f "${SUMO_BINARY_PATH}" ]]; then - set +o pipefail - "${SUMO_BINARY_PATH}" --version | grep -o 'v[0-9].*$' | sed 's/v//' - set -o pipefail - fi -} - # Ask to continue and abort if not function ask_to_continue() { if [[ "${CONTINUE}" == true ]]; then @@ -533,7 +421,7 @@ function ask_to_continue() { # Just fail if we're not running in uninteractive mode # TODO: Figure out a way to reliably ask for confirmation with stdin redirected - echo "Please use the --yes flag to continue" + echo "Please use the -y flag to continue" exit 1 # local choice @@ -548,47 +436,14 @@ function ask_to_continue() { } -# Print information about breaking changes -function print_breaking_changes() { - local versions - readonly versions="${1}" - - local changelog - changelog="$(echo -e "$(curl --retry 5 --connect-timeout 5 --max-time 30 --retry-delay 0 --retry-max-time 150 -sS https://raw.githubusercontent.com/SumoLogic/sumologic-otel-collector/main/CHANGELOG.md)")" - declare -r changelog - - local is_breaking_change - local message - message="" - - for version in ${versions}; do - # Print changelog for every version - is_breaking_change=$(echo -e "${changelog}" | grep -E '^## |^### Breaking|breaking changes' | sed -e '/## \[v'"${version}"'/,/## \[v/!d' | grep -E 'Breaking|breaking' || echo "") - - if [[ -n "${is_breaking_change}" ]]; then - if [[ -n "${message}" ]]; then - message="${message}, " - fi - message="${message}v${version}" - fi - done - - if [[ -n "${message}" ]]; then - echo "The following versions contain breaking changes: ${message}! Please make sure to read the linked Changelog file." - fi -} - # set up configuration function setup_config() { echo 'We are going to get and set up a default configuration for you' - echo "Generating configuration and saving as ${CONFIG_PATH}" + echo "Generating configuration and saving it in ${CONFIG_DIRECTORY}" if [[ "${REMOTELY_MANAGED}" == "true" ]]; then echo "Warning: remote management is currently in beta." - echo -e "Creating remote configurations directory (${REMOTE_CONFIG_DIRECTORY})" - mkdir -p "${REMOTE_CONFIG_DIRECTORY}" - write_opamp_extension if [[ -n "${SUMOLOGIC_INSTALLATION_TOKEN}" ]]; then @@ -607,26 +462,24 @@ function setup_config() { write_opamp_endpoint "${OPAMP_API_URL}" fi - write_tags "${FIELDS[@]}" + if [[ ${#FIELDS[@]} -gt 0 ]]; then + write_tags "${FIELDS[@]}" + fi - # Return/stop function execution + # Return/stop function execution early as remaining logic only applies + # to locally-managed installations return fi if [[ "${INSTALL_HOSTMETRICS}" == "true" ]]; then echo -e "Installing ${OS_TYPE} hostmetrics configuration" otelcol-config --enable-hostmetrics - if [[ "${OS_TYPE}" == "linux" ]]; then - echo -e "Setting the CAP_DAC_READ_SEARCH Linux capability on the collector binary to allow it to read host metrics from /proc directory: setcap 'cap_dac_read_search=ep' \"${SUMO_BINARY_PATH}\"" - echo -e "You can remove it with the following command: sudo setcap -r \"${SUMO_BINARY_PATH}\"" - echo -e "Without this capability, the collector will not be able to collect some of the host metrics." - # TODO(echlebek): remove this when it's supported in packaging - setcap 'cap_dac_read_search=ep' "${SUMO_BINARY_PATH}" - fi fi ## Check if there is anything to update in configuration if [[ -n "${SUMOLOGIC_INSTALLATION_TOKEN}" || -n "${API_BASE_URL}" || ${#FIELDS[@]} -ne 0 || "${EPHEMERAL}" == "true" ]]; then + USER_TOKEN="$(get_user_token)" + if [[ -n "${SUMOLOGIC_INSTALLATION_TOKEN}" && -z "${USER_TOKEN}" ]]; then write_installation_token "${SUMOLOGIC_INSTALLATION_TOKEN}" fi @@ -635,51 +488,54 @@ function setup_config() { write_ephemeral_true fi - # fill in api base url if [[ -n "${API_BASE_URL}" && -z "${USER_API_URL}" ]]; then write_api_url "${API_BASE_URL}" fi - # fill in opamp url - if [[ -n "${OPAMP_API_URL}" && -z "${USER_OPAMP_API_URL}" ]]; then - write_opamp_extension - write_opamp_endpoint "${OPAMP_API_URL}" + if [[ ${#FIELDS[@]} -gt 0 ]]; then + write_tags "${FIELDS[@]}" fi - - write_tags "${FIELDS[@]}" fi } function setup_config_darwin() { + echo 'We are going to get and set up a default configuration for you' + + echo "Generating configuration and saving it in ${CONFIG_DIRECTORY}" + if [[ "${REMOTELY_MANAGED}" == "true" ]]; then + echo "Warning: remote management is currently in beta." + + write_opamp_extension + + if [[ -n "${OPAMP_API_URL}" ]]; then + write_opamp_endpoint "${OPAMP_API_URL}" + fi + fi + if [[ "${EPHEMERAL}" == "true" ]]; then write_ephemeral_true fi - # fill in api base url - if [[ -n "${API_BASE_URL}" ]]; then + if [[ -n "${API_BASE_URL}" ]]; then write_api_url "${API_BASE_URL}" + elif [[ -n "${USER_API_URL}" ]]; then + write_api_url "${USER_API_URL}" fi - write_tags "${FIELDS[@]}" + if [[ ${#FIELDS[@]} -gt 0 ]]; then + write_tags "${FIELDS[@]}" + fi + # Return/stop function execution early as remaining logic only applies to + # locally-managed installations if [[ "${REMOTELY_MANAGED}" == "true" ]]; then - echo "Warning: remote management is currently in beta." - - echo -e "Creating remote configurations directory (${REMOTE_CONFIG_DIRECTORY})" - # TODO(echlebek): remove this once packaging does it - mkdir -p "${REMOTE_CONFIG_DIRECTORY}" - - write_opamp_extension - - write_remote_config_launchd "${LAUNCHD_CONFIG}" - - # Remote configuration directory must be writable - chmod 750 "${REMOTE_CONFIG_DIRECTORY}" - - # Remote configuration directory must be owned by the mac pkg service user - chown _otelcol-sumo:_otelcol-sumo "${REMOTE_CONFIG_DIRECTORY}" + return fi + if [[ "${INSTALL_HOSTMETRICS}" == "true" ]]; then + echo -e "Installing ${OS_TYPE} hostmetrics configuration" + otelcol-config --enable-hostmetrics + fi } # uninstall otelcol-sumo @@ -696,6 +552,29 @@ function uninstall() { echo "Uninstallation completed" } +function upgrade() { + case "${OS_TYPE}" in + "linux") upgrade_linux ;; + *) + echo "upgrading is not supported by this script for OS: ${OS_TYPE}" + exit 1 + ;; + esac + +} + +function upgrade_linux() { + case $(get_package_manager) in + yum | dnf) + yum update --quiet -y + ;; + apt-get) + apt-get update --quiet && apt-get upgrade --quiet -y + ;; + esac + +} + # uninstall otelcol-sumo on darwin function uninstall_darwin() { local UNINSTALL_SCRIPT_PATH @@ -716,7 +595,7 @@ function uninstall_darwin() { function uninstall_linux() { case $(get_package_manager) in yum | dnf) - yum remove --quiet -y otelcol-sumo + yum remove --quiet -yes otelcol-sumo ;; apt-get) if [[ "${PURGE}" == "true" ]]; then @@ -728,16 +607,6 @@ function uninstall_linux() { esac } -function escape_sed() { - local text - readonly text="${1}" - - # replaces `\` with `\\` and `/` with `\/` - echo "${text}" \ - | sed -e 's/\\/\\\\/g' \ - | sed -e 's|/|\\/|g' -} - function get_user_env_config() { local file readonly file="${1}" @@ -764,12 +633,37 @@ function get_user_env_config() { || echo "" } +function get_launchd_token() { + local file + readonly file="${1}" + + if [[ "${OS_TYPE}" != "darwin" ]]; then + return + fi + + if [[ ! -f "${file}" ]]; then + return + fi + + plutil_extract_key "${file}" "${LAUNCHD_TOKEN_KEY}" +} + function get_user_api_url() { - otelcol-config --read-kv .extensions.sumologic.api_base_url + if command -v otelcol-config &> /dev/null; then + KV=$(otelcol-config --read-kv .extensions.sumologic.api_base_url) + if [[ "${KV}" != "null" ]]; then + echo "${KV}" + fi + fi } function get_user_opamp_endpoint() { - otelcol-config --read-kv .extensions.opamp.endpoint + if command -v otelcol-config &> /dev/null; then + KV=$(otelcol-config --read-kv .extensions.opamp.endpoint) + if [[ "${KV}" != "null" ]]; then + echo "${KV}" + fi + fi } # write installation token to user configuration file @@ -780,27 +674,6 @@ function write_installation_token() { otelcol-config --set-installation-token "$token" } -# write ${ENV_TOKEN}" to systemd env configuration file -function write_installation_token_env() { - local token - readonly token="${1}" - - local file - readonly file="${2}" - - local token_name - token_name="${ENV_TOKEN}" - readonly token_name - - # ToDo: ensure we override only ${ENV_TOKEN}" env value - if grep "${token_name}" "${file}" > /dev/null 2>&1; then - # Do not expose token in sed command as it can be saw on processes list - echo "s/${token_name}=.*$/${token_name}=$(escape_sed "${token}")/" | sed -i.bak -f - "${file}" - else - echo "${token_name}=${token}" > "${file}" - fi -} - # write ${ENV_TOKEN} to launchd configuration file function write_installation_token_launchd() { local token @@ -824,33 +697,15 @@ function write_installation_token_launchd() { plutil_replace_key "${file}" "${LAUNCHD_ENV_KEY}" "xml" "" fi - # Create SUMOLOGIC_INSTALLATION_TOKEN key if it does not exist + # Create SUMOLOGIC_INSTALLATION_TOKEN key if it does not exist otherwise + # replace the SUMOLOGIC_INSTALLATION_TOKEN key if ! plutil_key_exists "${file}" "${LAUNCHD_TOKEN_KEY}"; then - plutil_create_key "${file}" "${LAUNCHD_TOKEN_KEY}" "string" "${SUMOLOGIC_INSTALLATION_TOKEN}" - fi - - # Replace SUMOLOGIC_INSTALLATION_TOKEN key if it has an incorrect type - if ! plutil_key_is_type "${LAUNCHD_CONFIG}" "${LAUNCHD_TOKEN_KEY}" "string"; then - plutil_replace_key "${LAUNCHD_CONFIG}" "${LAUNCHD_TOKEN_KEY}" "string" "${SUMOLOGIC_INSTALLATION_TOKEN}" + plutil_create_key "${file}" "${LAUNCHD_TOKEN_KEY}" "string" "${token}" + else + plutil_replace_key "${file}" "${LAUNCHD_TOKEN_KEY}" "string" "${token}" fi } -function write_remote_config_launchd() { - local file - readonly file="${1}" - - if [[ ! -f "${file}" ]]; then - echo "The LaunchDaemon configuration file is missing: ${file}" - exit 1 - fi - - # Delete existing ProgramArguments - plutil_delete_key "${file}" "ProgramArguments" - - # Create new ProgramArguments with --remote-config - plutil_create_key "${file}" "ProgramArguments" "json" "[ \"/usr/local/bin/otelcol-sumo\", \"--remote-config\", \"opamp:${CONFIG_PATH}\" ]" -} - # write sumologic ephemeral: true to user configuration file function write_ephemeral_true() { otelcol-config --enable-ephemeral @@ -887,81 +742,6 @@ function write_opamp_extension() { } # NB: this function is only for Darwin -function get_package_from_branch() { - local branch - readonly branch="${1}" - - local name - readonly name="${2}" - - local actions_url actions_output artifacts_link artifact_id - readonly actions_url="https://api.github.com/repos/${PACKAGE_GITHUB_ORG}/${PACKAGE_GITHUB_REPO}/actions/runs?status=success&branch=${branch}&event=push&per_page=1" - echo -e "Getting artifacts from latest CI run for branch \"${branch}\":\t\t${actions_url}" - actions_output="$(curl -f -sS \ - --connect-timeout 5 \ - --max-time 30 \ - --retry 5 \ - --retry-delay 0 \ - --retry-max-time 150 \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: token ${GITHUB_TOKEN}" \ - "${actions_url}")" - readonly actions_output - - # get latest action run - artifacts_link="$(echo "${actions_output}" | grep '"url"' | grep -oE '"https.*packaging/actions.*"' -m 1)" - - # strip first and last double-quote from $artifacts_link - artifacts_link=${artifacts_link%\"} - artifacts_link="${artifacts_link#\"}" - artifacts_link="${artifacts_link}/artifacts" - readonly artifacts_link - - echo -e "Getting artifact id for CI run:\t\t${artifacts_link}" - artifact_id="$(curl -f -sS \ - --connect-timeout 5 \ - --max-time 30 \ - --retry 5 \ - --retry-delay 0 \ - --retry-max-time 150 \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: token ${GITHUB_TOKEN}" \ - "${artifacts_link}" \ - | grep -E '"(id|name)"' \ - | grep -B 1 "\"${name}\"" -m 1 \ - | grep -oE "[0-9]+" -m 1)" - readonly artifact_id - - echo "Artifact ID: ${artifact_id}" - - local artifact_url download_path curl_args - readonly artifact_url="https://api.github.com/repos/${PACKAGE_GITHUB_ORG}/${PACKAGE_GITHUB_REPO}/actions/artifacts/${artifact_id}/zip" - readonly download_path="${DOWNLOAD_CACHE_DIR}/${artifact_id}.zip" - echo -e "Downloading package from: ${artifact_url}" - curl_args=( - "-fL" - "--connect-timeout" "5" - "--max-time" "${CURL_MAX_TIME}" - "--retry" "5" - "--retry-delay" "0" - "--retry-max-time" "150" - "--output" "${download_path}" - "--progress-bar" - ) - if [ "${KEEP_DOWNLOADS}" == "true" ]; then - curl_args+=("-z" "${download_path}") - fi - curl "${curl_args[@]}" \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: token ${GITHUB_TOKEN}" \ - "${artifact_url}" - - unzip -p "$download_path" > "${TMPDIR}/otelcol-sumo.pkg" - if [ "${KEEP_DOWNLOADS}" == "false" ]; then - rm -f "${download_path}" - fi -} - function get_package_from_url() { local url download_filename download_path curl_args readonly url="${1}" @@ -1005,17 +785,6 @@ function plutil_create_key() { fi } -function plutil_delete_key() { - local file key - readonly file="${1}" - readonly key="${2}" - - if ! plutil -remove "${key}" "${file}"; then - echo "plutil_delete_key error: key=${key}, file=${file}" - exit 1 - fi -} - function plutil_extract_key() { local file key output readonly file="${1}" @@ -1072,24 +841,55 @@ function get_package_manager() { } function install_linux_package() { - local package_with_version - readonly package_with_version="${1}" + local package_name + readonly package_name="${1}" + + if [[ "${PACKAGECLOUD_MASTER_TOKEN}" != "" ]]; then + base_url="https://${PACKAGECLOUD_MASTER_TOKEN}:@packages.sumologic.com" + else + base_url="https://packages.sumologic.com" + fi + base_url+="/install/repositories/${PACKAGECLOUD_ORG}/${PACKAGECLOUD_REPO}" + + repo_id="${PACKAGECLOUD_ORG}_${PACKAGECLOUD_REPO}" case $(get_package_manager) in yum | dnf) - curl -s https://packagecloud.io/install/repositories/sumologic/stable/script.rpm.sh | bash - yum --quiet --disablerepo="*" --enablerepo="sumologic_stable" -y update - yum install --quiet "${package_with_version}" + curl -s "${base_url}/script.rpm.sh" | bash + + local package_str + package_str="${package_name}" + if [[ -n "${VERSION}" ]]; then + package_str="${package_str}-${VERSION}" + fi + echo "Installing ${package_str}" + yum install --quiet -y "${package_str}" ;; apt-get) - curl -s https://packagecloud.io/install/repositories/sumologic/stable/script.deb.sh | bash - apt-get update --quiet -y -o Dir::Etc::sourcelist="sources.list.d/sumologic_stable" - apt-get install --quiet "${package_with_version}" + curl -s "${base_url}/script.deb.sh" | bash + apt-get update --quiet -y -o Dir::Etc::sourcelist="sources.list.d/${repo_id}" + + local package_str + package_str="${package_name}" + if [[ -n "${VERSION}" ]]; then + package_str="${package_str}=${VERSION}" + fi + echo "Installing ${package_str}" + apt-get install --quiet -y "${package_str}" ;; esac } function check_deprecated_linux_flags() { + if [[ -n "${BINARY_BRANCH}" ]]; then + echo "warning: --binary-branch is deprecated" + exit 1 + fi + + if [[ -n "${CONFIG_BRANCH}" ]]; then + echo "warning: --config-branch is deprecated" + fi + if [[ "${OS_TYPE}" == "darwin" ]]; then return fi @@ -1098,17 +898,114 @@ function check_deprecated_linux_flags() { echo "--download-only is only supported on darwin, use 'install.sh --upgrade' to upgrade otelcol-sumo" exit 1 fi +} - if [[ -n "${BINARY_BRANCH}" ]]; then - echo "--binary-branch is only supported on darwin, use --version, --channel, and --channel-token on linux" - exit 1 +function is_package_installed() { + case $(get_package_manager) in + yum | dnf) + # TODO: refine exact command + yum --cacheonly list --installed otelcol-sumo > /dev/null 2>&1 + ;; + apt-get) + dpkg --status otelcol-sumo > /dev/null 2>&1 + ;; + esac +} + +# Try to infer if there is a binary, pre-packaging rework installation, the +# kind of installation that was performed by downloading artifacts from Github, +# before we moved to using distribution packages. +function has_prepackaging_installation() { + if command -v otelcol-sumo > /dev/null 2>&1 && ! is_package_installed; then + true + else + false fi +} - if [[ -n "${CONFIG_BRANCH}" ]]; then - echo "warning: --config-branch is deprecated" +function backup_prepackaging_configuration() { + cp -r "${CONFIG_DIRECTORY}" "${TMPDIR}/otelcol-sumo-configuration-backup" +} + +function restore_prepackaging_configuration() { + echo "restore_prepackaging_configuration(): not implemented yet" +} + +function uninstall_prepackaging_installation() { + # Stop the service and remove its unit file + SYSTEMD_SERVICE_PATH="/etc/systemd/system/otelcol-sumo.service" + if [[ -f "${SYSTEMD_SERVICE_PATH}" ]]; then + systemctl --quiet stop otelcol-sumo || true + systemctl --quiet disable otelcol-sumo || true + rm -f "${SYSTEMD_SERVICE_PATH}" + fi + + # Remove the old binary + rm -f "${SUMO_BINARY_PATH}" + + # Remove old configuration and data + FILE_STORAGE="/var/lib/otelcol-sumo/file_storage" + rm -rf "${CONFIG_DIRECTORY}" "${FILE_STORAGE}" + + # Remove the otelcol-sumo user and group + SYSTEM_USER="otelcol-sumo" + userdel --remove --force "${SYSTEM_USER}" 2>/dev/null || true + groupdel "${SYSTEM_USER}" 2>/dev/null || true +} + +function is_package_installed() { + case $(get_package_manager) in + yum | dnf) + # TODO: refine exact command + yum --cacheonly list --installed otelcol-sumo > /dev/null 2>&1 + ;; + apt-get) + dpkg --status otelcol-sumo > /dev/null 2>&1 + ;; + esac +} + +# Try to infer if there is a binary, pre-packaging rework installation, the +# kind of installation that was performed by downloading artifacts from Github, +# before we moved to using distribution packages. +function has_prepackaging_installation() { + if command -v otelcol-sumo > /dev/null 2>&1 && ! is_package_installed; then + true + else + false fi } +function backup_prepackaging_configuration() { + cp -r "${CONFIG_DIRECTORY}" "${TMPDIR}/otelcol-sumo-configuration-backup" +} + +function restore_prepackaging_configuration() { + echo "restore_prepackaging_configuration(): not implemented yet" +} + +function uninstall_prepackaging_installation() { + # Stop the service and remove its unit file + SYSTEMD_SERVICE_PATH="/etc/systemd/system/otelcol-sumo.service" + if [[ -f "${SYSTEMD_SERVICE_PATH}" ]]; then + systemctl --quiet stop otelcol-sumo || true + systemctl --quiet disable otelcol-sumo || true + rm -f "${SYSTEMD_SERVICE_PATH}" + fi + + # Remove the old binary + rm -f "${SUMO_BINARY_PATH}" + + # Remove old configuration and data + FILE_STORAGE="/var/lib/otelcol-sumo/file_storage" + rm -rf "${CONFIG_DIRECTORY}" "${FILE_STORAGE}" + + # Remove the otelcol-sumo user and group + SYSTEM_USER="otelcol-sumo" + userdel --remove --force "${SYSTEM_USER}" 2>/dev/null || true + groupdel "${SYSTEM_USER}" 2>/dev/null || true +} + ############################ Main code OS_TYPE="$(get_os_type)" @@ -1125,7 +1022,7 @@ check_dependencies check_deprecated_linux_flags readonly SUMOLOGIC_INSTALLATION_TOKEN API_BASE_URL OPAMP_API_URL FIELDS CONTINUE CONFIG_DIRECTORY UNINSTALL -readonly USER_ENV_DIRECTORY CONFIG_DIRECTORY CONFIG_PATH COMMON_CONFIG_PATH +readonly USER_ENV_DIRECTORY CONFIG_DIRECTORY COMMON_CONFIG_PATH readonly INSTALL_HOSTMETRICS readonly REMOTELY_MANAGED readonly CURL_MAX_TIME @@ -1135,49 +1032,67 @@ if [[ "${UNINSTALL}" == "true" ]]; then uninstall exit 0 fi +if [[ "${UPGRADE}" == "true" ]]; then + upgrade + exit 0 +fi + +# get_installation_token returns the value of SUMOLOGIC_INSTALLATION_TOKEN +# (set by a flag or environment variable) when it is not empty, otherwise it +# will attempt to fetch the token from an existing installation and return it. +function get_installation_token() { + local token="" + + if [[ -z "${token}" ]]; then + token="${SUMOLOGIC_INSTALLATION_TOKEN}" + fi + + if [[ -z "${token}" ]]; then + token="$(get_user_token)" + fi + + echo "${token}" +} # Attempt to find a token from an existing installation -if command -v otelcol-config &> /dev/null; then - USER_TOKEN=$(otelcol-config --read-kv .extensions.sumologic.installation_token) -fi -if [[ -z "${USER_TOKEN}" ]]; then - USER_TOKEN="$(get_user_env_config "${TOKEN_ENV_FILE}")" -fi -readonly USER_TOKEN +function get_user_token() { + local token="${USER_TOKEN}" -# Exit if installation token is not set and there is no user configuration -if [[ -z "${SUMOLOGIC_INSTALLATION_TOKEN}" && "${SKIP_TOKEN}" != "true" && -z "${USER_TOKEN}" && -z "${DOWNLOAD_ONLY}" ]]; then - echo "Installation token has not been provided. Please set the '${ENV_TOKEN}' environment variable." - echo "You can ignore this requirement by adding '--${ARG_LONG_SKIP_TOKEN} argument." - exit 1 -fi + # Attempt to find a token from an existing installation + # Check the systemd env file for a token + if [[ -f "${TOKEN_ENV_FILE}" && -z "${token}" ]]; then + token="$(get_user_env_config "${TOKEN_ENV_FILE}")" + fi -# verify if passed arguments are the same like in user's configuration -if [[ -z "${DOWNLOAD_ONLY}" ]]; then - if [[ -n "${USER_TOKEN}" && -n "${SUMOLOGIC_INSTALLATION_TOKEN}" && "${USER_TOKEN}" != "${SUMOLOGIC_INSTALLATION_TOKEN}" ]]; then - echo "You are trying to install with different token than in your configuration file!" - exit 1 - fi + # Check the launchd config for a token + if [[ -f "${LAUNCHD_CONFIG}" && -z "${token}" ]]; then + token="$(get_launchd_token "${LAUNCHD_CONFIG}")" + fi - USER_API_URL="$(get_user_api_url)" - if [[ -n "${USER_API_URL}" && -n "${API_BASE_URL}" && "${USER_API_URL}" != "${API_BASE_URL}" ]]; then - echo "You are trying to install with different api base url than in your configuration file!" - exit 1 + # Check yaml configuration for a token + if [[ -z "${token}" ]]; then + if command -v otelcol-config &> /dev/null; then + local output="" + output=$(otelcol-config --read-kv .extensions.sumologic.installation_token) + if [[ "${output}" != "null" ]]; then + token="${output}" + fi fi + fi - USER_OPAMP_API_URL="$(get_user_opamp_endpoint "${COMMON_CONFIG_PATH}")" - if [[ -n "${USER_OPAMP_API_URL}" && -n "${OPAMP_API_URL}" && "${USER_OPAMP_API_URL}" != "${OPAMP_API_URL}" ]]; then - echo "You are trying to install with different opamp endpoint than in your configuration file!" - exit 1 - fi -fi + echo "${token}" +} -set +u -if [[ -n "${BINARY_BRANCH}" && -z "${GITHUB_TOKEN}" ]]; then - echo "GITHUB_TOKEN env is required for '${ARG_LONG_BINARY_BRANCH}' option" - exit 1 +# Load & cache user token +USER_TOKEN="$(get_user_token)" + +# Exit if installation token is not set by flag, environment variable, or from +# existing installation configuration. Skip this check when DOWNLOAD_ONLY is set +# which is only possible on macOS. +if [[ -z "$(get_installation_token)" && -z "${DOWNLOAD_ONLY}" ]]; then + echo "Installation token has not been provided. Please set the '${ENV_TOKEN}' environment variable." + exit 1 fi -set -u if [ "${FIPS}" == "true" ]; then case "${OS_TYPE}" in @@ -1195,6 +1110,27 @@ if [ "${FIPS}" == "true" ]; then fi if [[ "${OS_TYPE}" == "darwin" ]]; then + # verify if passed arguments are the same like in user's configuration + if [[ -z "${DOWNLOAD_ONLY}" ]]; then + USER_TOKEN="$(get_user_token)" + if [[ -n "${USER_TOKEN}" && -n "${SUMOLOGIC_INSTALLATION_TOKEN}" && "${USER_TOKEN}" != "${SUMOLOGIC_INSTALLATION_TOKEN}" ]]; then + echo "You are trying to install with different token than in your configuration file!" + exit 1 + fi + + USER_API_URL="$(get_user_api_url)" + if [[ -n "${USER_API_URL}" && -n "${API_BASE_URL}" && "${USER_API_URL}" != "${API_BASE_URL}" ]]; then + echo "You are trying to install with different api base url than in your configuration file! (${USER_API_URL} != ${API_BASE_URL})" + exit 1 + fi + + USER_OPAMP_API_URL="$(get_user_opamp_endpoint "${COMMON_CONFIG_PATH}")" + if [[ -n "${USER_OPAMP_API_URL}" && -n "${OPAMP_API_URL}" && "${USER_OPAMP_API_URL}" != "${OPAMP_API_URL}" ]]; then + echo "You are trying to install with different opamp endpoint than in your configuration file!" + exit 1 + fi + fi + package_arch="" case "${ARCH_TYPE}" in "amd64") package_arch="intel" ;; @@ -1206,88 +1142,43 @@ if [[ "${OS_TYPE}" == "darwin" ]]; then esac readonly package_arch - if [[ "${SKIP_CONFIG}" == "true" ]]; then - echo "SKIP_CONFIG is not supported on darwin" - exit 1 - fi - - echo -e "Getting versions..." - # Get versions, but ignore errors as we fallback to other methods later - VERSIONS="$(get_package_versions || echo "")" + pkg_url="" - # Use user's version if set, otherwise get latest version from API (or website) - if [[ -z "${VERSION}" ]]; then - VERSION="$(get_latest_package_version "${VERSIONS}")" - fi - - readonly VERSIONS VERSION + if [[ -z "${DARWIN_PKG_URL}" ]]; then + # Use user's version if set, otherwise get latest version from API (or website) + if [[ -z "${VERSION}" ]]; then + echo -e "Getting latest version..." + VERSION="$(get_latest_s3_package_version)" + fi - echo -e "Version to install:\t${VERSION}" + readonly VERSION - package_suffix="${package_arch}.pkg" + echo -e "Version to install:\t${VERSION}" - if [[ -n "${BINARY_BRANCH}" ]]; then - artifact_name="otelcol-sumo_.*-${package_suffix}" - get_package_from_branch "${BINARY_BRANCH}" "${artifact_name}" - else - artifact_name="otelcol-sumo_${VERSION}-${package_suffix}" + artifact_name="otelcol-sumo_${VERSION}-${package_arch}.pkg" readonly artifact_name - LINK="https://github.com/${PACKAGE_GITHUB_ORG}/${PACKAGE_GITHUB_REPO}/releases/download/v${VERSION}/${artifact_name}" - readonly LINK - - get_package_from_url "${LINK}" + pkg_url="${S3_URI}/${VERSION}/${artifact_name}" + else + pkg_url="${DARWIN_PKG_URL}" fi + get_package_from_url "${pkg_url}" + pkg="${TMPDIR}/otelcol-sumo.pkg" - choices="${TMPDIR}/otelcol-sumo-choices.xml" - readonly pkg choices if [[ "${DOWNLOAD_ONLY}" == "true" ]]; then echo "Package downloaded to: ${pkg}" exit 0 fi - # Extract choices xml from meta package, override the choices to enable - # optional choices, and then install using the new choice selections - installer -showChoiceChangesXML -pkg "${pkg}" -target / > "${choices}" - - # Determine how many installation choices exist - choices_count=$(plutil -convert raw -o - "${choices}") - readonly choices_count - - # Loop through each installation choice - for (( i=0; i < "${choices_count}"; i++ )); do - choice_id_key="${i}.choiceIdentifier" - choice_attr_key="${i}.choiceAttribute" - attr_setting_key="${i}.attributeSetting" - - # Skip if choiceAttribute does not equal selected - choice_attr="$(plutil_extract_key "${choices}" "${choice_attr_key}")" - if [ "$choice_attr" != "selected" ]; then - continue - fi - - # Get the choice identifier - choice_id="$(plutil_extract_key "${choices}" "${choice_id_key}")" - - # Mark the choice as selected if the feature flag is true - case "${choice_id}" in - "otelcol-sumo-hostmetricsChoice") - if [[ "${INSTALL_HOSTMETRICS}" == "true" ]]; then - echo -e "Enabling ${OS_TYPE} hostmetrics install option" - plutil_replace_key "${choices}" "${attr_setting_key}" "integer" 1 - fi - ;; - esac - done - - installer -applyChoiceChangesXML "$choices" -pkg "$pkg" -target / + echo "Installing otelcol-sumo package" + installer -pkg "${pkg}" -target / - if [[ -n "${SUMOLOGIC_INSTALLATION_TOKEN}" && -z "${USER_TOKEN}" && "${SKIP_TOKEN}" != "true" ]]; then - echo "Writing installation token to launchd config" - write_installation_token_launchd "${SUMOLOGIC_INSTALLATION_TOKEN}" "${LAUNCHD_CONFIG}" - fi + # The token must be written to the launchd config on every install as + # upgrades replace the launchd config + echo "Writing installation token to launchd config" + write_installation_token_launchd "$(get_installation_token)" "${LAUNCHD_CONFIG}" setup_config_darwin @@ -1297,81 +1188,62 @@ if [[ "${OS_TYPE}" == "darwin" ]]; then echo "Waiting for otelcol to start" while ! launchctl print system/otelcol-sumo | grep -q "state = running"; do - sleep 0.1 + echo -n " otelcol service " + launchctl print system/otelcol-sumo | grep "state = " + sleep 1 done OTEL_EXITED_WITH_ERROR=false echo 'Checking otelcol status' - for i in {1..15}; do + for _ in {1..15}; do if launchctl print system/otelcol-sumo | grep -q "last exit code = 1"; then OTEL_EXITED_WITH_ERROR=true break; fi - sleep 1 + sleep 0.4 done if [[ "${OTEL_EXITED_WITH_ERROR}" == "true" ]]; then echo "Failed to launch otelcol" tail /var/log/otelcol-sumo/otelcol-sumo.log exit 1 fi + echo "Successfully started otelcol" exit 0 fi -echo -e "Getting installed version..." -INSTALLED_VERSION="$(get_installed_version)" -echo -e "Installed version:\t${INSTALLED_VERSION:-none}" - -echo -e "Getting versions..." -# Get versions, but ignore errors as we fallback to other methods later -VERSIONS="$(get_versions || echo "")" - -# Use user's version if set, otherwise get latest version from API (or website) -if [[ -z "${VERSION}" ]]; then - VERSION="$(get_latest_version "${VERSIONS}")" +package_name="" +if [[ "${FIPS}" == "true" ]]; then + echo "Getting FIPS-compliant binary" + package_name=otelcol-sumo-fips +else + package_name=otelcol-sumo fi -echo -e "Version to install:\t${VERSION}" +if has_prepackaging_installation; then + # Display a warning and information message here? + echo 'Pre-packaging installation detected' -# Check if otelcol is already in newest version -if [[ "${INSTALLED_VERSION}" == "${VERSION}" ]]; then - echo -e "OpenTelemetry collector is already in newest (${VERSION}) version" -else + # Backup current configuration + backup_prepackaging_configuration - # add newline before breaking changes and changelog - echo "" - if [[ -n "${INSTALLED_VERSION}" ]]; then - # Take versions from installed up to the newest - BETWEEN_VERSIONS="$(get_versions_from "${VERSIONS}" "${INSTALLED_VERSION}")" - readonly BETWEEN_VERSIONS - print_breaking_changes "${BETWEEN_VERSIONS}" - fi + # Remove current installation + uninstall_prepackaging_installation - echo -e "Changelog:\t\thttps://github.com/SumoLogic/sumologic-otel-collector/blob/main/CHANGELOG.md" - # add newline after breaking changes and changelog - echo "" - - package_with_version="${VERSION}" - if [[ -n "${package_with_version}" ]]; then - if [[ "${FIPS}" == "true" ]]; then - echo "Getting FIPS-compliant binary" - package_with_version=otelcol-sumo-fips - else - package_with_version=otelcol-sumo - fi - fi - - install_linux_package "${package_with_version}" - - verify_installation + # We can now proceed and install using the packages and attempt to restore + # the configuration later. + HAD_PREPACKAGING_INSTALLATION="true" fi -if [[ "${SKIP_CONFIG}" == "false" ]]; then - setup_config -fi +install_linux_package "${package_name}" +verify_installation +setup_config -if [[ -n "${SUMOLOGIC_INSTALLATION_TOKEN}" && -z "${USER_TOKEN}" ]]; then - echo 'Writing installation token to env file' - write_installation_token_env "${SUMOLOGIC_INSTALLATION_TOKEN}" "${TOKEN_ENV_FILE}" +# If an old, pre-packaging rework installation was removed during this run, +# attempt the restore the configuration that was backed up during that removal. +set +u +if [[ -n "${HAD_PREPACKAGING_INSTALLATION}" ]]; then + restore_prepackaging_configuration fi +set -u echo 'Reloading systemd' systemctl daemon-reload @@ -1382,11 +1254,4 @@ systemctl enable otelcol-sumo echo 'Starting otelcol-sumo service' systemctl restart otelcol-sumo -echo 'Waiting 10s before checking status' -sleep 10 -if ! systemctl status otelcol-sumo --no-pager; then - echo "Failed to launch otelcol" - exit 1 -fi - exit 0 diff --git a/install-script/test/Makefile b/install-script/test/Makefile index 85c2eed1..335c4041 100644 --- a/install-script/test/Makefile +++ b/install-script/test/Makefile @@ -3,18 +3,24 @@ ifeq ($(OS),Windows_NT) endif ifneq ($(OS),windows) - GOTESTPREFIX ?= sudo env PATH="${PATH}" GH_CI_TOKEN="${GITHUB_TOKEN}" + GOTESTPREFIX ?= sudo env PATH="${PATH}" GH_CI_TOKEN="${GITHUB_TOKEN}" DARWIN_PKG_URL="${DARWIN_PKG_URL}" PACKAGECLOUD_MASTER_TOKEN="${PACKAGECLOUD_MASTER_TOKEN}" PACKAGECLOUD_REPO="${PACKAGECLOUD_REPO}" OTC_VERSION="${OTC_VERSION}" OTC_BUILD_NUMBER="${OTC_BUILD_NUMBER}" endif LINT=golangci-lint GOTEST=go test GOTESTBINARY=sumologic_scripts_tests.test +GOTESTNAME ?= "" +GOTESTRUN= + +ifneq ($(GOTESTNAME),"") + GOTESTRUN=-test.run $(GOTESTNAME) +endif # We build the test binary separately to avoid downloading modules as root .PHONY: test test: $(GOTEST) -c - $(GOTESTPREFIX) ./$(GOTESTBINARY) -test.v + $(GOTESTPREFIX) ./$(GOTESTBINARY) -test.v $(GOTESTRUN) .PHONY: fmt fmt: diff --git a/install-script/test/check.go b/install-script/test/check.go index a362d953..53dbc2a6 100644 --- a/install-script/test/check.go +++ b/install-script/test/check.go @@ -7,7 +7,7 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" ) type check struct { @@ -26,197 +26,234 @@ func checkSkipTest(c check) bool { return false } -type checkFunc func(check) +type checkFunc func(check) bool -func checkBinaryCreated(c check) { - require.FileExists(c.test, binaryPath, "binary has not been created") +func checkBinaryCreated(c check) bool { + return assert.FileExists(c.test, binaryPath, "binary has not been created") } -func checkBinaryNotCreated(c check) { - require.NoFileExists(c.test, binaryPath, "binary is already created") +func checkBinaryNotCreated(c check) bool { + return assert.NoFileExists(c.test, binaryPath, "binary is already created") } -func checkBinaryIsRunning(c check) { +func checkBinaryIsRunning(c check) bool { cmd := exec.Command(binaryPath, "--version") err := cmd.Start() - require.NoError(c.test, err, "error while checking version") + if !assert.NoError(c.test, err, "error while checking version") { + return false + } code, err := exitCode(cmd) - require.NoError(c.test, err, "error while checking exit code") - require.Equal(c.test, 0, code, "got error code while checking version") + assert.NoError(c.test, err, "error while checking exit code") + assert.Equal(c.test, 0, code, "got error code while checking version") + return true } -func checkRun(c check) { - require.Equal(c.test, c.expectedInstallCode, c.code, "unexpected installation script error code") +func checkRun(c check) bool { + return assert.Equal(c.test, c.expectedInstallCode, c.code, "unexpected installation script error code") } -func checkConfigCreated(c check) { - require.FileExists(c.test, configPath, "configuration has not been created properly") +func checkConfigCreated(c check) bool { + return assert.FileExists(c.test, configPath, "configuration has not been created properly") } -func checkConfigNotCreated(c check) { - require.NoFileExists(c.test, configPath, "configuration has been created") +func checkConfigNotCreated(c check) bool { + return assert.NoFileExists(c.test, configPath, "configuration has been created") } -func checkConfigOverrided(c check) { +func checkConfigOverrided(c check) bool { conf, err := getConfig(configPath) - require.NoError(c.test, err) + if err != nil { + c.test.Error(err) + return false + } - require.Condition(c.test, func() (success bool) { - switch conf.Extensions.Sumologic.InstallationToken { - case "${SUMOLOGIC_INSTALLATION_TOKEN}": - return true - default: - return false - } - }, "invalid value for installation token") + if got, want := conf.Extensions.Sumologic.InstallationToken, "${SUMOLOGIC_INSTALLATION_TOKEN}"; got != want { + c.test.Errorf("bad installation token: got %q, want %q", got, want) + } + return true } -func checkUserConfigCreated(c check) { - require.FileExists(c.test, userConfigPath, "user configuration has not been created properly") +func checkUserConfigCreated(c check) bool { + return assert.FileExists(c.test, userConfigPath, "user configuration has not been created properly") } -func checkUserConfigNotCreated(c check) { - require.NoFileExists(c.test, userConfigPath, "user configuration has been created") +func checkUserConfigNotCreated(c check) bool { + return assert.NoFileExists(c.test, userConfigPath, "user configuration has been created") } -func checkHomeDirectoryCreated(c check) { - require.DirExists(c.test, libPath, "home directory has not been created properly") +func checkHomeDirectoryCreated(c check) bool { + return assert.DirExists(c.test, libPath, "home directory has not been created properly") } -func checkNoBakFilesPresent(c check) { +func checkNoBakFilesPresent(c check) bool { cwd, err := os.Getwd() - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } cwdGlob := filepath.Join(cwd, "*.bak") etcPathGlob := filepath.Join(etcPath, "*.bak") etcPathNestedGlob := filepath.Join(etcPath, "*", "*.bak") for _, bakGlob := range []string{cwdGlob, etcPathGlob, etcPathNestedGlob} { bakFiles, err := filepath.Glob(bakGlob) - require.NoError(c.test, err) - require.Empty(c.test, bakFiles) + if !assert.NoError(c.test, err) { + return false + } + if !assert.Empty(c.test, bakFiles) { + return false + } } + return true } -func checkOpAmpEndpointSet(c check) { - conf, err := getConfig(configPath) - require.NoError(c.test, err, "error while reading configuration") +func checkOpAmpEndpointSet(c check) bool { + conf, err := getConfig(sumoRemotePath) + if !assert.NoError(c.test, err, "error while reading configuration") { + return false + } - require.Equal(c.test, conf.Extensions.OpAmp.Endpoint, "wss://example.com") + if !assert.Equal(c.test, conf.Extensions.OpAmp.Endpoint, "wss://example.com") { + return false + } + return true } -func checkHostmetricsConfigCreated(c check) { - require.FileExists(c.test, hostmetricsConfigPath, "hostmetrics configuration has not been created properly") +func checkHostmetricsConfigCreated(c check) bool { + return assert.FileExists(c.test, hostmetricsConfigPath, "hostmetrics configuration has not been created properly") } -func checkHostmetricsConfigNotCreated(c check) { - require.NoFileExists(c.test, hostmetricsConfigPath, "hostmetrics configuration has been created") +func checkHostmetricsConfigNotCreated(c check) bool { + return assert.NoFileExists(c.test, hostmetricsConfigPath, "hostmetrics configuration has been created") } -func checkRemoteConfigDirectoryCreated(c check) { - require.DirExists(c.test, opampDPath, "remote configuration directory has not been created properly") +func checkRemoteConfigDirectoryCreated(c check) bool { + return assert.DirExists(c.test, opampDPath, "remote configuration directory has not been created properly") } -func checkRemoteConfigDirectoryNotCreated(c check) { - require.NoDirExists(c.test, opampDPath, "remote configuration directory has been created") +func checkRemoteConfigDirectoryNotCreated(c check) bool { + return assert.NoDirExists(c.test, opampDPath, "remote configuration directory has been created") } -func checkTags(c check) { +func checkTags(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err, "error while reading configuration") + if !assert.NoError(c.test, err, "error while reading configuration") { + return false + } + errored := false for k, v := range c.installOptions.tags { - require.Equal(c.test, v, conf.Extensions.Sumologic.Tags[k], "tag is different than expected") + if !assert.Equal(c.test, v, conf.Extensions.Sumologic.Tags[k], "tag is different than expected") { + errored = true + } } + return !errored } -func checkDifferentTags(c check) { +func checkDifferentTags(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err, "error while reading configuration") + if !assert.NoError(c.test, err, "error while reading configuration") { + return false + } - require.Equal(c.test, "tag", conf.Extensions.Sumologic.Tags["some"], "tag is different than expected") + return assert.Equal(c.test, "tag", conf.Extensions.Sumologic.Tags["some"], "tag is different than expected") } -func checkAbortedDueToDifferentToken(c check) { - require.Greater(c.test, len(c.output), 0) - require.Contains(c.test, c.output[len(c.output)-1], "You are trying to install with different token than in your configuration file!") +func checkAbortedDueToDifferentToken(c check) bool { + if !assert.Greater(c.test, len(c.output), 0) { + return false + } + return assert.Contains(c.test, c.output[len(c.output)-1], "You are trying to install with different token than in your configuration file!") } -func preActionWriteAPIBaseURLToUserConfig(c check) { +func preActionWriteAPIBaseURLToUserConfig(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } conf.Extensions.Sumologic.APIBaseURL = c.installOptions.apiBaseURL err = saveConfig(userConfigPath, conf) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionWriteDifferentAPIBaseURLToUserConfig(c check) { +func preActionWriteDifferentAPIBaseURLToUserConfig(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } conf.Extensions.Sumologic.APIBaseURL = "different" + c.installOptions.apiBaseURL err = saveConfig(userConfigPath, conf) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionWriteDifferentTagsToUserConfig(c check) { +func preActionWriteDifferentTagsToUserConfig(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } conf.Extensions.Sumologic.Tags = map[string]string{ "some": "tag", } err = saveConfig(userConfigPath, conf) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionWriteEmptyUserConfig(c check) { +func preActionWriteEmptyUserConfig(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } err = saveConfig(userConfigPath, conf) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionWriteTagsToUserConfig(c check) { +func preActionWriteTagsToUserConfig(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } conf.Extensions.Sumologic.Tags = c.installOptions.tags err = saveConfig(userConfigPath, conf) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func checkAbortedDueToDifferentAPIBaseURL(c check) { - require.Greater(c.test, len(c.output), 0) - require.Contains(c.test, c.output[len(c.output)-1], "You are trying to install with different api base url than in your configuration file!") +func checkAbortedDueToDifferentAPIBaseURL(c check) bool { + if !assert.Greater(c.test, len(c.output), 0) { + return false + } + return assert.Contains(c.test, c.output[len(c.output)-1], "You are trying to install with different api base url than in your configuration file!") } -func checkAPIBaseURLInConfig(c check) { +func checkAPIBaseURLInConfig(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err, "error while reading configuration") - - require.Equal(c.test, c.installOptions.apiBaseURL, conf.Extensions.Sumologic.APIBaseURL, "api base url is different than expected") -} + if !assert.NoError(c.test, err, "error while reading configuration") { + return false + } -func checkAbortedDueToDifferentTags(c check) { - require.Greater(c.test, len(c.output), 0) - require.Contains(c.test, c.output[len(c.output)-1], "You are trying to install with different tags than in your configuration file!") + return assert.Equal(c.test, c.installOptions.apiBaseURL, conf.Extensions.Sumologic.APIBaseURL, "api base url is different than expected") } -func PathHasPermissions(t *testing.T, path string, perms uint32) { +func PathHasPermissions(t *testing.T, path string, perms uint32) bool { info, err := os.Stat(path) - require.NoError(t, err) + if !assert.NoError(t, err) { + return false + } expected := fs.FileMode(perms) got := info.Mode().Perm() - require.Equal(t, expected, got, "%s should have %o permissions but has %o", path, expected, got) + return assert.Equal(t, expected, got, "%s should have %o permissions but has %o", path, expected, got) } -func PathHasUserACL(t *testing.T, path string, ownerName string, perms string) { +func PathHasUserACL(t *testing.T, path string, ownerName string, perms string) bool { cmd := exec.Command("/usr/bin/getfacl", path) output, err := cmd.Output() - require.NoError(t, err, "error while checking "+path+" acl") - require.Contains(t, string(output), "user:"+ownerName+":"+perms) + if !assert.NoError(t, err, "error while checking "+path+" acl") { + return false + } + return assert.Contains(t, string(output), "user:"+ownerName+":"+perms) } diff --git a/install-script/test/check_darwin.go b/install-script/test/check_darwin.go index d874f1cc..d59df2f1 100644 --- a/install-script/test/check_darwin.go +++ b/install-script/test/check_darwin.go @@ -3,16 +3,16 @@ package sumologic_scripts_tests import ( "io/fs" "os" + "path" "path/filepath" "regexp" "strings" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func checkConfigFilesOwnershipAndPermissions(ownerName string, ownerGroup string) func(c check) { - return func(c check) { +func checkConfigFilesOwnershipAndPermissions(ownerName string, ownerGroup string) func(c check) bool { + return func(c check) bool { PathHasPermissions(c.test, etcPath, etcPathPermissions) PathHasOwner(c.test, etcPath, ownerName, ownerGroup) @@ -21,11 +21,15 @@ func checkConfigFilesOwnershipAndPermissions(ownerName string, ownerGroup string for _, glob := range []string{etcPathGlob, etcPathNestedGlob} { paths, err := filepath.Glob(glob) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } for _, path := range paths { var permissions uint32 info, err := os.Stat(path) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } if info.IsDir() { switch path { case etcPath: @@ -41,9 +45,6 @@ func checkConfigFilesOwnershipAndPermissions(ownerName string, ownerGroup string case configPath: // /etc/otelcol-sumo/sumologic.yaml permissions = configPathFilePermissions - case userConfigPath: - // /etc/otelcol-sumo/conf.d/common.yaml - permissions = commonConfigPathFilePermissions default: // /etc/otelcol-sumo/conf.d/* permissions = confDPathFilePermissions @@ -54,46 +55,57 @@ func checkConfigFilesOwnershipAndPermissions(ownerName string, ownerGroup string } } PathHasPermissions(c.test, configPath, configPathFilePermissions) + + return true } } -func checkDifferentTokenInLaunchdConfig(c check) { - require.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") +func checkDifferentTokenInLaunchdConfig(c check) bool { + if !assert.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") { + return false + } conf, err := getLaunchdConfig(launchdPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } - require.Equal(c.test, "different"+c.installOptions.installToken, conf.EnvironmentVariables.InstallationToken, "installation token is different than expected") + return assert.Equal(c.test, "different"+c.installOptions.installToken, conf.EnvironmentVariables.InstallationToken, "installation token is different than expected") } -func checkGroupExists(c check) { - exists := dsclKeyExistsForPath(c.test, "/Groups", systemGroup) - require.True(c.test, exists, "group has not been created") +func checkGroupExists(c check) bool { + exists, err := dsclKeyExistsForPath(c.test, "/Groups", systemGroup) + assert.NoError(c.test, err) + return assert.True(c.test, exists, "group has not been created") } -func checkGroupNotExists(c check) { - exists := dsclKeyExistsForPath(c.test, "/Groups", systemGroup) - require.False(c.test, exists, "group has been created") +func checkGroupNotExists(c check) bool { + exists, err := dsclKeyExistsForPath(c.test, "/Groups", systemGroup) + assert.NoError(c.test, err) + return assert.False(c.test, exists, "group has been created") } -func checkHostmetricsOwnershipAndPermissions(ownerName string, ownerGroup string) func(c check) { - return func(c check) { +func checkHostmetricsOwnershipAndPermissions(ownerName string, ownerGroup string) func(c check) bool { + return func(c check) bool { PathHasOwner(c.test, hostmetricsConfigPath, ownerName, ownerGroup) PathHasPermissions(c.test, hostmetricsConfigPath, confDPathFilePermissions) + return true } } -func checkLaunchdConfigCreated(c check) { - require.FileExists(c.test, launchdPath, "launchd configuration has not been created properly") +func checkLaunchdConfigCreated(c check) bool { + return assert.FileExists(c.test, launchdPath, "launchd configuration has not been created properly") } -func checkLaunchdConfigNotCreated(c check) { - require.NoFileExists(c.test, launchdPath, "launchd configuration has been created") +func checkLaunchdConfigNotCreated(c check) bool { + return assert.NoFileExists(c.test, launchdPath, "launchd configuration has been created") } -func checkPackageCreated(c check) { +func checkPackageCreated(c check) bool { re, err := regexp.Compile("Package downloaded to: .*/otelcol-sumo.pkg") - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } matchedLine := "" for _, line := range c.output { @@ -101,96 +113,100 @@ func checkPackageCreated(c check) { matchedLine = line } } - require.NotEmpty(c.test, matchedLine, "package path not in output") + if !assert.NotEmpty(c.test, matchedLine, "package path not in output") { + return false + } packagePath := strings.TrimPrefix(matchedLine, "Package downloaded to: ") - require.FileExists(c.test, packagePath, "package has not been created") + return assert.FileExists(c.test, packagePath, "package has not been created") } -func checkTokenInLaunchdConfig(c check) { - require.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") +func checkTokenInLaunchdConfig(c check) bool { + if !assert.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") { + return false + } conf, err := getLaunchdConfig(launchdPath) - require.NoError(c.test, err) - - require.Equal(c.test, c.installOptions.installToken, conf.EnvironmentVariables.InstallationToken, "installation token is different than expected") -} - -func checkEphemeralInConfig(p string) func(c check) { - return func(c check) { - assert.True(c.test, c.installOptions.ephemeral, "ephemeral was not specified") - - conf, err := getConfig(p) - require.NoError(c.test, err, "error while reading configuration") - - assert.True(c.test, conf.Extensions.Sumologic.Ephemeral, "ephemeral is not true") + if !assert.NoError(c.test, err) { + return false } -} - -func checkEphemeralNotInConfig(p string) func(c check) { - return func(c check) { - assert.False(c.test, c.installOptions.ephemeral, "ephemeral was specified") - conf, err := getConfig(p) - require.NoError(c.test, err, "error while reading configuration") - - assert.False(c.test, conf.Extensions.Sumologic.Ephemeral, "ephemeral is true") - } + return assert.Equal(c.test, c.installOptions.installToken, conf.EnvironmentVariables.InstallationToken, "installation token is different than expected") } -func checkUserExists(c check) { - exists := dsclKeyExistsForPath(c.test, "/Users", systemUser) - require.True(c.test, exists, "user has not been created") +func checkUserExists(c check) bool { + exists, err := dsclKeyExistsForPath(c.test, "/Users", systemUser) + assert.NoError(c.test, err) + return assert.True(c.test, exists, "user has not been created") } -func checkUserNotExists(c check) { - exists := dsclKeyExistsForPath(c.test, "/Users", systemUser) - require.False(c.test, exists, "user has been created") +func checkUserNotExists(c check) bool { + exists, err := dsclKeyExistsForPath(c.test, "/Users", systemUser) + assert.NoError(c.test, err) + return assert.False(c.test, exists, "user has been created") } -func preActionInstallPackage(c check) { +func preActionInstallPackage(c check) bool { + c.installOptions.installToken = installToken + c.installOptions.apiBaseURL = mockAPIBaseURL c.code, c.output, c.errorOutput, c.err = runScript(c) + return assert.NoError(c.test, c.err) } -func preActionInstallPackageWithDifferentAPIBaseURL(c check) { - c.installOptions.apiBaseURL = "different" + c.installOptions.apiBaseURL +func preActionInstallPackageWithDifferentAPIBaseURL(c check) bool { + c.installOptions.installToken = installToken + c.installOptions.apiBaseURL = path.Join(c.installOptions.apiBaseURL, "different") c.code, c.output, c.errorOutput, c.err = runScript(c) + return assert.NoError(c.test, c.err) } -func preActionInstallPackageWithDifferentTags(c check) { +func preActionInstallPackageWithDifferentTags(c check) bool { + c.installOptions.installToken = installToken c.installOptions.tags = map[string]string{ "some": "tag", } c.code, c.output, c.errorOutput, c.err = runScript(c) + return assert.NoError(c.test, c.err) } -func preActionInstallPackageWithNoAPIBaseURL(c check) { - c.installOptions.apiBaseURL = "" +func preActionInstallPackageWithNoAPIBaseURL(c check) bool { + c.installOptions.installToken = installToken + c.installOptions.apiBaseURL = emptyAPIBaseURL c.code, c.output, c.errorOutput, c.err = runScript(c) + return assert.NoError(c.test, c.err) } -func preActionInstallPackageWithNoTags(c check) { +func preActionInstallPackageWithNoTags(c check) bool { + c.installOptions.installToken = installToken c.installOptions.tags = nil c.code, c.output, c.errorOutput, c.err = runScript(c) + return assert.NoError(c.test, c.err) } -func preActionMockLaunchdConfig(c check) { +func preActionMockLaunchdConfig(c check) bool { f, err := os.Create(launchdPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } err = f.Chmod(fs.FileMode(launchdPathFilePermissions)) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } conf := NewLaunchdConfig() + conf.EnvironmentVariables.InstallationToken = installToken err = saveLaunchdConfig(launchdPath, conf) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionWriteDifferentTokenToLaunchdConfig(c check) { +func preActionWriteDifferentTokenToLaunchdConfig(c check) bool { conf, err := getLaunchdConfig(launchdPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } conf.EnvironmentVariables.InstallationToken = "different" + c.installOptions.installToken err = saveLaunchdConfig(launchdPath, conf) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } diff --git a/install-script/test/check_linux.go b/install-script/test/check_linux.go index 23e5cf28..03e9364f 100644 --- a/install-script/test/check_linux.go +++ b/install-script/test/check_linux.go @@ -6,264 +6,251 @@ import ( "os" "os/exec" "os/user" - "path/filepath" "strconv" "strings" "testing" "github.com/joho/godotenv" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func checkACLAvailability(c check) bool { return assert.FileExists(&testing.T{}, "/usr/bin/getfacl", "File ACLS is not supported") } -func checkConfigFilesOwnershipAndPermissions(ownerName string, ownerGroup string) func(c check) { - return func(c check) { - etcPathGlob := filepath.Join(etcPath, "*") - etcPathNestedGlob := filepath.Join(etcPath, "*", "*") - - for _, glob := range []string{etcPathGlob, etcPathNestedGlob} { - paths, err := filepath.Glob(glob) - require.NoError(c.test, err) - for _, path := range paths { - var permissions uint32 - info, err := os.Stat(path) - require.NoError(c.test, err) - if info.IsDir() { - if path == opampDPath { - permissions = opampDPermissions - } else { - permissions = configPathDirPermissions - } - } else { - permissions = configPathFilePermissions - } - PathHasPermissions(c.test, path, permissions) - PathHasOwner(c.test, configPath, ownerName, ownerGroup) - } - } - PathHasPermissions(c.test, configPath, configPathFilePermissions) - } -} - -func checkDifferentTokenInConfig(c check) { +func checkDifferentTokenInConfig(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err, "error while reading configuration") + if !assert.NoError(c.test, err, "error while reading configuration") { + return false + } - require.Equal(c.test, "different"+c.installOptions.installToken, conf.Extensions.Sumologic.InstallationToken, "installation token is different than expected") + return assert.Equal(c.test, "different"+c.installOptions.installToken, conf.Extensions.Sumologic.InstallationToken, "installation token is different than expected") } -func checkDifferentTokenInEnvFile(c check) { - require.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") +func checkDifferentTokenInEnvFile(c check) bool { + if !assert.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") { + return false + } envs, err := godotenv.Read(tokenEnvFilePath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } if _, ok := envs["SUMOLOGIC_INSTALL_TOKEN"]; ok { - require.Equal(c.test, "different"+c.installOptions.installToken, envs["SUMOLOGIC_INSTALL_TOKEN"], "installation token is different than expected") + if !assert.Equal(c.test, "different"+c.installOptions.installToken, envs["SUMOLOGIC_INSTALL_TOKEN"], "installation token is different than expected") { + return false + } } else { - require.Equal(c.test, "different"+c.installOptions.installToken, envs["SUMOLOGIC_INSTALLATION_TOKEN"], "installation token is different than expected") + if !assert.Equal(c.test, "different"+c.installOptions.installToken, envs["SUMOLOGIC_INSTALLATION_TOKEN"], "installation token is different than expected") { + return false + } } + return true } -func checkDownloadTimeout(c check) { +func checkDownloadTimeout(c check) bool { output := strings.Join(c.errorOutput, "\n") count := strings.Count(output, "Operation timed out after") - require.Equal(c.test, 6, count) + return assert.Equal(c.test, 6, count) } -func checkHostmetricsOwnershipAndPermissions(ownerName string, ownerGroup string) func(c check) { - return func(c check) { +func checkHostmetricsOwnershipAndPermissions(ownerName string, ownerGroup string) func(c check) bool { + return func(c check) bool { PathHasOwner(c.test, hostmetricsConfigPath, ownerName, ownerGroup) PathHasPermissions(c.test, hostmetricsConfigPath, configPathFilePermissions) + return true } } -func checkOutputUserAddWarnings(c check) { +func checkOutputUserAddWarnings(c check) bool { output := strings.Join(c.output, "\n") - require.NotContains(c.test, output, "useradd", "unexpected useradd output") + if !assert.NotContains(c.test, output, "useradd", "unexpected useradd output") { + return false + } errOutput := strings.Join(c.errorOutput, "\n") - require.NotContains(c.test, errOutput, "useradd", "unexpected useradd output") -} - -func checkTokenEnvFileCreated(c check) { - require.FileExists(c.test, tokenEnvFilePath, "env token file has not been created") + return assert.NotContains(c.test, errOutput, "useradd", "unexpected useradd output") } -func checkTokenEnvFileNotCreated(c check) { - require.NoFileExists(c.test, tokenEnvFilePath, "env token file not been created") +func checkTokenEnvFileCreated(c check) bool { + return assert.FileExists(c.test, tokenEnvFilePath, "env token file has not been created") } -func checkTokenInConfig(c check) { - require.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") - - conf, err := getConfig(userConfigPath) - require.NoError(c.test, err, "error while reading configuration") - - require.Equal(c.test, c.installOptions.installToken, conf.Extensions.Sumologic.InstallationToken, "installation token is different than expected") -} - -func checkTokenInSumoConfig(c check) { - require.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") - - conf, err := getConfig(configPath) - require.NoError(c.test, err, "error while reading configuration") - - require.Equal(c.test, c.installOptions.installToken, conf.Extensions.Sumologic.InstallationToken, "installation token is different than expected") +func checkTokenEnvFileNotCreated(c check) bool { + return assert.NoFileExists(c.test, tokenEnvFilePath, "env token file has been created") } -func checkTokenInEnvFile(c check) { - require.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") +func checkTokenInEnvFile(c check) bool { + if !assert.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") { + return false + } envs, err := godotenv.Read(tokenEnvFilePath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } if _, ok := envs["SUMOLOGIC_INSTALL_TOKEN"]; ok { - require.Equal(c.test, c.installOptions.installToken, envs["SUMOLOGIC_INSTALL_TOKEN"], "installation token is different than expected") + if !assert.Equal(c.test, c.installOptions.installToken, envs["SUMOLOGIC_INSTALL_TOKEN"], "installation token is different than expected") { + return false + } } else { - require.Equal(c.test, c.installOptions.installToken, envs["SUMOLOGIC_INSTALLATION_TOKEN"], "installation token is different than expected") - } -} - -func checkEphemeralInConfig(p string) func(c check) { - return func(c check) { - assert.True(c.test, c.installOptions.ephemeral, "ephemeral was not specified") - - conf, err := getConfig(p) - require.NoError(c.test, err, "error while reading configuration") - - assert.True(c.test, conf.Extensions.Sumologic.Ephemeral, "ephemeral is not true") + if !assert.Equal(c.test, c.installOptions.installToken, envs["SUMOLOGIC_INSTALLATION_TOKEN"], "installation token is different than expected") { + return false + } } + return true } -func checkEphemeralNotInConfig(p string) func(c check) { - return func(c check) { - assert.False(c.test, c.installOptions.ephemeral, "ephemeral was specified") - - conf, err := getConfig(p) - require.NoError(c.test, err, "error while reading configuration") - - assert.False(c.test, conf.Extensions.Sumologic.Ephemeral, "ephemeral is true") +func checkUninstallationOutput(c check) bool { + if !assert.Greater(c.test, len(c.output), 1) { + return false } + return assert.Contains(c.test, c.output[len(c.output)-1], "Uninstallation completed") } -func checkUninstallationOutput(c check) { - require.Greater(c.test, len(c.output), 1) - require.Contains(c.test, c.output[len(c.output)-1], "Uninstallation completed") -} - -func checkUserExists(c check) { +func checkUserExists(c check) bool { _, err := user.Lookup(systemUser) - require.NoError(c.test, err, "user has not been created") + return assert.NoError(c.test, err, "user has not been created") } -func checkVarLogACL(c check) { +func checkVarLogACL(c check) bool { if !checkACLAvailability(c) { - return + return true } PathHasUserACL(c.test, "/var/log", systemUser, "r-x") + return true } -func preActionCreateHomeDirectory(c check) { +func preActionCreateHomeDirectory(c check) bool { err := os.MkdirAll(libPath, fs.FileMode(etcPathPermissions)) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } // preActionCreateUser creates the system user and then set it as owner of configPath -func preActionCreateUser(c check) { - preActionMockUserConfig(c) +func preActionCreateUser(c check) bool { + if !preActionMockUserConfig(c) { + return false + } cmd := exec.Command("useradd", systemUser) _, err := cmd.CombinedOutput() - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } f, err := os.Open(configPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } user, err := user.Lookup(systemUser) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } uid, err := strconv.Atoi(user.Uid) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } gid, err := strconv.Atoi(user.Gid) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } err = f.Chown(uid, gid) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionMockConfigs(c check) { - preActionMockConfig(c) - preActionMockUserConfig(c) +func preActionMockConfigs(c check) bool { + if !preActionMockConfig(c) { + return false + } + return preActionMockUserConfig(c) } -func preActionMockEnvFiles(c check) { +func preActionMockEnvFiles(c check) bool { err := os.MkdirAll(envDirectoryPath, fs.FileMode(etcPathPermissions)) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } f, err := os.Create(configPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } err = f.Chmod(fs.FileMode(configPathFilePermissions)) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionMockStructure(c check) { - preActionMockConfigs(c) +func preActionMockStructure(c check) bool { + if !preActionMockConfigs(c) { + return false + } err := os.MkdirAll(fileStoragePath, os.ModePerm) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } content := []byte("#!/bin/sh\necho hello world\n") err = os.WriteFile(binaryPath, content, 0755) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionWriteDefaultAPIBaseURLToUserConfig(c check) { +func preActionWriteDefaultAPIBaseURLToUserConfig(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } conf.Extensions.Sumologic.APIBaseURL = apiBaseURL err = saveConfig(userConfigPath, conf) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionWriteDifferentDeprecatedTokenToEnvFile(c check) { - preActionMockEnvFiles(c) +func preActionWriteDifferentDeprecatedTokenToEnvFile(c check) bool { + if !preActionMockEnvFiles(c) { + return false + } content := fmt.Sprintf("SUMOLOGIC_INSTALL_TOKEN=different%s", c.installOptions.installToken) err := os.WriteFile(tokenEnvFilePath, []byte(content), fs.FileMode(etcPathPermissions)) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionWriteDifferentTokenToEnvFile(c check) { - preActionMockEnvFiles(c) +func preActionWriteDifferentTokenToEnvFile(c check) bool { + if !preActionMockEnvFiles(c) { + return false + } content := fmt.Sprintf("SUMOLOGIC_INSTALLATION_TOKEN=different%s", c.installOptions.installToken) err := os.WriteFile(tokenEnvFilePath, []byte(content), fs.FileMode(etcPathPermissions)) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionWriteDifferentTokenToUserConfig(c check) { +func preActionWriteDifferentTokenToUserConfig(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } conf.Extensions.Sumologic.InstallationToken = "different" + c.installOptions.installToken err = saveConfig(userConfigPath, conf) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionWriteTokenToUserConfig(c check) { +func preActionWriteTokenToUserConfig(c check) bool { conf, err := getConfig(userConfigPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } conf.Extensions.Sumologic.InstallationToken = c.installOptions.installToken err = saveConfig(userConfigPath, conf) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } diff --git a/install-script/test/check_unix.go b/install-script/test/check_unix.go index 71e3fad7..f1683b39 100644 --- a/install-script/test/check_unix.go +++ b/install-script/test/check_unix.go @@ -10,43 +10,116 @@ import ( "syscall" "testing" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" ) -func checkAbortedDueToNoToken(c check) { - require.Greater(c.test, len(c.output), 1) - require.Contains(c.test, c.output[len(c.output)-2], "Installation token has not been provided. Please set the 'SUMOLOGIC_INSTALLATION_TOKEN' environment variable.") - require.Contains(c.test, c.output[len(c.output)-1], "You can ignore this requirement by adding '--skip-installation-token argument.") +type configRoot struct { + Extensions *configExtensions `yaml:"extensions,omitempty"` } -func preActionMockConfig(c check) { +type configExtensions struct { + Sumologic *sumologicExt `yaml:"sumologic,omitempty"` +} + +type sumologicExt struct { + Ephemeral bool `yaml:"ephemeral,omitempty"` +} + +func checkAbortedDueToNoToken(c check) bool { + if !assert.Greater(c.test, len(c.output), 1) { + return false + } + return assert.Contains(c.test, c.output, "Installation token has not been provided. Please set the 'SUMOLOGIC_INSTALLATION_TOKEN' environment variable.") +} + +func checkEphemeralConfigFileCreated(p string) func(c check) bool { + return func(c check) bool { + return assert.FileExists(c.test, p, "ephemeral config file has not been created") + } +} + +func checkEphemeralConfigFileNotCreated(p string) func(c check) bool { + return func(c check) bool { + return assert.NoFileExists(c.test, p, "ephemeral config file has been created") + } +} + +func checkEphemeralEnabledInRemote(p string) func(c check) bool { + return func(c check) bool { + yamlFile, err := os.ReadFile(p) + if assert.NoError(c.test, err, "sumologic remote config file could not be read") { + return false + } + + var config configRoot + + if assert.NoError(c.test, yaml.Unmarshal(yamlFile, &config), "could not parse yaml") { + return false + } + + return config.Extensions.Sumologic.Ephemeral + } +} + +func checkEphemeralNotEnabledInRemote(p string) func(c check) bool { + return func(c check) bool { + yamlFile, err := os.ReadFile(p) + if err != nil { + // assume the error is due to the file not existing, which is valid + return true + } + + var config configRoot + + if assert.NoError(c.test, yaml.Unmarshal(yamlFile, &config), "could not parse yaml") { + return false + } + + return !config.Extensions.Sumologic.Ephemeral + } +} + +func preActionMockConfig(c check) bool { err := os.MkdirAll(etcPath, fs.FileMode(etcPathPermissions)) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } f, err := os.Create(configPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } err = f.Chmod(fs.FileMode(configPathFilePermissions)) - require.NoError(c.test, err) + return assert.NoError(c.test, err) } -func preActionMockUserConfig(c check) { +func preActionMockUserConfig(c check) bool { err := os.MkdirAll(etcPath, fs.FileMode(etcPathPermissions)) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } err = os.MkdirAll(confDPath, fs.FileMode(configPathDirPermissions)) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } f, err := os.Create(userConfigPath) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } - err = f.Chmod(fs.FileMode(commonConfigPathFilePermissions)) - require.NoError(c.test, err) + err = f.Chmod(fs.FileMode(confDPathFilePermissions)) + return assert.NoError(c.test, err) } -func PathHasOwner(t *testing.T, path string, ownerName string, groupName string) { +func PathHasOwner(t *testing.T, path string, ownerName string, groupName string) bool { info, err := os.Stat(path) - require.NoError(t, err) + if !assert.NoError(t, err) { + return false + } // get the owning user and group stat := info.Sys().(*syscall.Stat_t) @@ -54,11 +127,17 @@ func PathHasOwner(t *testing.T, path string, ownerName string, groupName string) gid := strconv.FormatUint(uint64(stat.Gid), 10) usr, err := user.LookupId(uid) - require.NoError(t, err) + if !assert.NoError(t, err) { + return false + } group, err := user.LookupGroupId(gid) - require.NoError(t, err) - - require.Equal(t, ownerName, usr.Username, "%s should be owned by user '%s'", path, ownerName) - require.Equal(t, groupName, group.Name, "%s should be owned by group '%s'", path, groupName) + if !assert.NoError(t, err) { + return false + } + + if !assert.Equal(t, ownerName, usr.Username, "%s should be owned by user '%s'", path, ownerName) { + return false + } + return assert.Equal(t, groupName, group.Name, "%s should be owned by group '%s'", path, groupName) } diff --git a/install-script/test/check_windows.go b/install-script/test/check_windows.go index 1a406db5..6b9246a3 100644 --- a/install-script/test/check_windows.go +++ b/install-script/test/check_windows.go @@ -12,7 +12,6 @@ import ( "unsafe" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "golang.org/x/sys/windows" ) @@ -29,79 +28,109 @@ type ACLRecord struct { AccessMode windows.ACCESS_MODE } -func checkAbortedDueToNoToken(c check) { - require.Greater(c.test, len(c.output), 1) - require.Greater(c.test, len(c.errorOutput), 1) +func checkAbortedDueToNoToken(c check) bool { + if !assert.Greater(c.test, len(c.output), 1) { + return false + } + if !assert.Greater(c.test, len(c.errorOutput), 1) { + return false + } // The exact formatting of the error message can be different depending on Powershell version errorOutput := strings.Join(c.errorOutput, " ") - require.Contains(c.test, errorOutput, "Installation token has not been provided.") - require.Contains(c.test, errorOutput, "Please set the SUMOLOGIC_INSTALLATION_TOKEN environment variable.") + if !assert.Contains(c.test, errorOutput, "Installation token has not been provided.") { + return false + } + return assert.Contains(c.test, errorOutput, "Please set the SUMOLOGIC_INSTALLATION_TOKEN environment variable.") } -func checkBinaryFipsError(c check) { +func checkBinaryFipsError(c check) bool { cmd := exec.Command(binaryPath, "--version") _, err := cmd.Output() - require.Error(c.test, err, "running on a non-FIPS system must error") + if !assert.Error(c.test, err, "running on a non-FIPS system must error") { + return false + } exitErr, ok := err.(*exec.ExitError) - require.True(c.test, ok, "returned error must be of type ExitError") + if !assert.True(c.test, ok, "returned error must be of type ExitError") { + return false + } - require.Equal(c.test, 2, exitErr.ExitCode(), "got error code while checking version") - require.Contains(c.test, string(exitErr.Stderr), "not in FIPS mode") + if !assert.Equal(c.test, 2, exitErr.ExitCode(), "got error code while checking version") { + return false + } + return assert.Contains(c.test, string(exitErr.Stderr), "not in FIPS mode") } -func checkEphemeralNotInConfig(p string) func(c check) { - return func(c check) { +func checkEphemeralNotInConfig(p string) func(c check) bool { + return func(c check) bool { assert.False(c.test, c.installOptions.ephemeral, "ephemeral was specified") conf, err := getConfig(p) - require.NoError(c.test, err, "error while reading configuration") + if !assert.NoError(c.test, err, "error while reading configuration") { + return false + } assert.False(c.test, conf.Extensions.Sumologic.Ephemeral, "ephemeral is true") + return true } } -func checkEphemeralInConfig(p string) func(c check) { - return func(c check) { +func checkEphemeralInConfig(p string) func(c check) bool { + return func(c check) bool { assert.True(c.test, c.installOptions.ephemeral, "ephemeral was not specified") conf, err := getConfig(p) - require.NoError(c.test, err, "error while reading configuration") + if !assert.NoError(c.test, err, "error while reading configuration") { + return false + } assert.True(c.test, conf.Extensions.Sumologic.Ephemeral, "ephemeral is not true") + return true } } -func checkTokenInConfig(c check) { - require.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") +func checkTokenInConfig(c check) bool { + if !assert.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") { + return false + } conf, err := getConfig(userConfigPath) - require.NoError(c.test, err, "error while reading configuration") + if !assert.NoError(c.test, err, "error while reading configuration") { + return false + } - require.Equal(c.test, c.installOptions.installToken, conf.Extensions.Sumologic.InstallationToken, "installation token is different than expected") + return assert.Equal(c.test, c.installOptions.installToken, conf.Extensions.Sumologic.InstallationToken, "installation token is different than expected") } -func checkTokenInSumoConfig(c check) { - require.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") +func checkTokenInSumoConfig(c check) bool { + if !assert.NotEmpty(c.test, c.installOptions.installToken, "installation token has not been provided") { + return false + } conf, err := getConfig(configPath) - require.NoError(c.test, err, "error while reading configuration") + if !assert.NoError(c.test, err, "error while reading configuration") { + return false + } - require.Equal(c.test, c.installOptions.installToken, conf.Extensions.Sumologic.InstallationToken, "installation token is different than expected") + return assert.Equal(c.test, c.installOptions.installToken, conf.Extensions.Sumologic.InstallationToken, "installation token is different than expected") } -func checkConfigFilesOwnershipAndPermissions(ownerSid string) func(c check) { - return func(c check) { +func checkConfigFilesOwnershipAndPermissions(ownerSid string) func(c check) bool { + return func(c check) bool { etcPathGlob := filepath.Join(etcPath, "*") etcPathNestedGlob := filepath.Join(etcPath, "*", "*") for _, glob := range []string{etcPathGlob, etcPathNestedGlob} { paths, err := filepath.Glob(glob) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } for _, path := range paths { var aclRecords []ACLRecord info, err := os.Stat(path) - require.NoError(c.test, err) + if !assert.NoError(c.test, err) { + return false + } if info.IsDir() { if path == opampDPath { aclRecords = opampDPermissions @@ -115,38 +144,48 @@ func checkConfigFilesOwnershipAndPermissions(ownerSid string) func(c check) { PathHasOwner(c.test, path, ownerSid) } } + return true } } -func PathHasOwner(t *testing.T, path string, ownerSID string) { +func PathHasOwner(t *testing.T, path string, ownerSID string) bool { securityDescriptor, err := windows.GetNamedSecurityInfo( path, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION, ) - require.NoError(t, err) + if !assert.NoError(t, err) { + return false + } // get the owning user owner, _, err := securityDescriptor.Owner() - require.NoError(t, err) + if !assert.NoError(t, err) { + return false + } - require.Equal(t, ownerSID, owner.String(), "%s should be owned by user '%s'", path, ownerSID) + return assert.Equal(t, ownerSID, owner.String(), "%s should be owned by user '%s'", path, ownerSID) } -func PathHasWindowsACLs(t *testing.T, path string, expectedACLs []ACLRecord) { +func PathHasWindowsACLs(t *testing.T, path string, expectedACLs []ACLRecord) bool { securityDescriptor, err := windows.GetNamedSecurityInfo( path, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION, ) - require.NoError(t, err) + if !assert.NoError(t, err) { + return false + } // get the ACL entries acl, _, err := securityDescriptor.DACL() - require.NoError(t, err) - require.NotNil(t, acl) + if !assert.NoError(t, err) || !assert.NotNil(t, acl) { + return false + } entries, err := GetExplicitEntriesFromACL(acl) - require.NoError(t, err) + if !assert.NoError(t, err) { + return false + } aclRecords := []ACLRecord{} for _, entry := range entries { aclRecord := ExplicitEntryToACLRecord(entry) @@ -155,6 +194,7 @@ func PathHasWindowsACLs(t *testing.T, path string, expectedACLs []ACLRecord) { } } assert.Equal(t, expectedACLs, aclRecords, "invalid ACLs for %s", path) + return true } // GetExplicitEntriesFromACL gets a list of explicit entries from an ACL diff --git a/install-script/test/command_unix.go b/install-script/test/command_unix.go index 05d0c40a..3ac98f76 100644 --- a/install-script/test/command_unix.go +++ b/install-script/test/command_unix.go @@ -9,15 +9,12 @@ import ( "os" "os/exec" "strings" - - "github.com/stretchr/testify/require" ) type installOptions struct { installToken string autoconfirm bool tags map[string]string - skipConfig bool skipInstallToken bool fips bool envs map[string]string @@ -30,6 +27,7 @@ type installOptions struct { opampEndpoint string downloadOnly bool dontKeepDownloads bool + version string } func (io *installOptions) string() []string { @@ -45,8 +43,8 @@ func (io *installOptions) string() []string { opts = append(opts, "--fips") } - if io.skipConfig { - opts = append(opts, "--skip-config") + if io.downloadOnly { + opts = append(opts, "--download-only") } if io.skipInstallToken { @@ -76,8 +74,19 @@ func (io *installOptions) string() []string { } } - if io.apiBaseURL != "" { - opts = append(opts, "--api", io.apiBaseURL) + // 1. If the apiBaseURL is empty, replace it with the mock API's URL. + // 2. If the apiBaseURL is equal to the emptyAPIBaseURL constant, don't set + // the --api flag. + // 3. If none of the above are true, set the --api flag to the value of + // apiBaseURL. + apiBaseURL := "" + if io.apiBaseURL == "" { + apiBaseURL = mockAPIBaseURL + } else if io.apiBaseURL != emptyAPIBaseURL { + apiBaseURL = io.apiBaseURL + } + if apiBaseURL != "" { + opts = append(opts, "--api", apiBaseURL) } if io.timeout != 0 { @@ -88,6 +97,15 @@ func (io *installOptions) string() []string { opts = append(opts, "--opamp-api", io.opampEndpoint) } + otc_version := os.Getenv("OTC_VERSION") + otc_build_number := os.Getenv("OTC_BUILD_NUMBER") + + if io.version != "" { + opts = append(opts, "--version", io.version) + } else if otc_version != "" && otc_build_number != "" { + opts = append(opts, "--version", fmt.Sprintf("%s-%s", otc_version, otc_build_number)) + } + return opts } @@ -124,22 +142,24 @@ func runScript(ch check) (int, []string, []string, error) { cmd.Env = ch.installOptions.buildEnvs() output := []string{} + ch.test.Logf("Running command: %s", strings.Join(ch.installOptions.string(), " ")) + in, err := cmd.StdinPipe() if err != nil { - require.NoError(ch.test, err) + return 0, nil, nil, err } defer in.Close() out, err := cmd.StdoutPipe() if err != nil { - require.NoError(ch.test, err) + return 0, nil, nil, err } defer out.Close() errOut, err := cmd.StderrPipe() if err != nil { - require.NoError(ch.test, err) + return 0, nil, nil, err } defer errOut.Close() @@ -148,7 +168,7 @@ func runScript(ch check) (int, []string, []string, error) { // Start the process if err = cmd.Start(); err != nil { - require.NoError(ch.test, err) + return 0, nil, nil, err } // Read the results from the process @@ -167,8 +187,9 @@ func runScript(ch check) (int, []string, []string, error) { } // otherwise ensure there is no error - require.NoError(ch.test, err) - + if err != nil { + return 0, nil, nil, err + } } // Handle stderr separately diff --git a/install-script/test/command_windows.go b/install-script/test/command_windows.go index 47ad5b86..cea52c45 100644 --- a/install-script/test/command_windows.go +++ b/install-script/test/command_windows.go @@ -7,8 +7,6 @@ import ( "os" "os/exec" "strings" - - "github.com/stretchr/testify/require" ) type installOptions struct { @@ -50,6 +48,8 @@ func (io *installOptions) string() []string { if io.apiBaseURL != "" { opts = append(opts, "-Api", io.apiBaseURL) + } else { + opts = append(opts, "-Api", mockAPIBaseURL) } return opts @@ -90,20 +90,20 @@ func runScript(ch check) (int, []string, []string, error) { in, err := cmd.StdinPipe() if err != nil { - require.NoError(ch.test, err) + return 0, nil, nil, err } defer in.Close() out, err := cmd.StdoutPipe() if err != nil { - require.NoError(ch.test, err) + return 0, nil, nil, err } defer out.Close() errOut, err := cmd.StderrPipe() if err != nil { - require.NoError(ch.test, err) + return 0, nil, nil, err } defer errOut.Close() @@ -112,7 +112,7 @@ func runScript(ch check) (int, []string, []string, error) { // Start the process if err = cmd.Start(); err != nil { - require.NoError(ch.test, err) + return 0, nil, nil, err } // Read the results from the process @@ -131,8 +131,9 @@ func runScript(ch check) (int, []string, []string, error) { } // otherwise ensure there is no error - require.NoError(ch.test, err) - + if err != nil { + return 0, nil, nil, err + } } // Handle stderr separately diff --git a/install-script/test/common.go b/install-script/test/common.go index f82b3aa8..3af4b9c0 100644 --- a/install-script/test/common.go +++ b/install-script/test/common.go @@ -1,5 +1,12 @@ package sumologic_scripts_tests +import ( + "io" + "net" + "net/http" + "testing" +) + type testSpec struct { name string options installOptions @@ -9,3 +16,29 @@ type testSpec struct { conditionalChecks []condCheckFunc installCode int } + +func startMockAPI(t *testing.T) (*http.Server, error) { + t.Log("Starting HTTP server") + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if _, err := io.WriteString(w, "200 OK\n"); err != nil { + panic(err) + } + }) + + listener, err := net.Listen("tcp", ":3333") + if err != nil { + return nil, err + } + + httpServer := &http.Server{ + Handler: mux, + } + go func() { + err := httpServer.Serve(listener) + if err != nil && err != http.ErrServerClosed { + panic(err) + } + }() + return httpServer, nil +} diff --git a/install-script/test/common_darwin.go b/install-script/test/common_darwin.go index a4f1311c..7197c49f 100644 --- a/install-script/test/common_darwin.go +++ b/install-script/test/common_darwin.go @@ -9,14 +9,16 @@ import ( "strings" "testing" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" ) -func dsclDeletePath(t *testing.T, path string) { +func dsclDeletePath(t *testing.T, path string) bool { cmd := exec.Command("dscl", ".", "-delete", path) output, err := cmd.CombinedOutput() - require.NoErrorf(t, err, "error while using dscl to delete path: %s, path: %s", output, path) - require.Empty(t, string(output)) + if !assert.NoErrorf(t, err, "error while using dscl to delete path: %s, path: %s", output, path) { + return false + } + return assert.Empty(t, string(output)) } // The user.Lookup() and user.LookupGroup() functions do not appear to work @@ -24,25 +26,25 @@ func dsclDeletePath(t *testing.T, path string) { // has been deleted. There are several GitHub issues in github.com/golang/go // that describe similar or related behaviour. To work around this issue we use // the dscl command to determine if a user or group exists. -func dsclKeyExistsForPath(t *testing.T, path, key string) bool { +func dsclKeyExistsForPath(t *testing.T, path, key string) (bool, error) { cmd := exec.Command("dscl", ".", "-list", path) out, err := cmd.StdoutPipe() if err != nil { - require.NoError(t, err) + return false, err } defer out.Close() bufOut := bufio.NewReader(out) if err := cmd.Start(); err != nil { - require.NoError(t, err) + return false, err } for { line, _, err := bufOut.ReadLine() if string(line) == key { - return true + return true, nil } // exit if script finished @@ -51,82 +53,131 @@ func dsclKeyExistsForPath(t *testing.T, path, key string) bool { } // otherwise ensure there is no error - require.NoError(t, err) + if err != nil { + return false, err + } } - return false + return false, nil } -func forgetPackage(t *testing.T, name string) { +func forgetPackage(t *testing.T, name string) error { noReceiptMsg := fmt.Sprintf("No receipt for '%s' found at '/'.", name) output, err := exec.Command("pkgutil", "--forget", name).CombinedOutput() if err != nil && !strings.Contains(string(output), noReceiptMsg) { - require.NoErrorf(t, err, "error forgetting package: %s", string(output)) + return fmt.Errorf("error forgetting package: %s", string(output)) } + return nil } -func removeFileIfExists(t *testing.T, path string) { +func removeFileIfExists(t *testing.T, path string) error { if _, err := os.Stat(path); err != nil { - return + if os.IsNotExist(err) { + return nil + } + return err } - require.NoErrorf(t, os.Remove(path), "error removing file: %s", path) + if err := os.Remove(path); err != nil { + return fmt.Errorf("error removing file: %s", path) + } + return nil } -func removeDirectoryIfExists(t *testing.T, path string) { +func removeDirectoryIfExists(t *testing.T, path string) error { info, err := os.Stat(path) if err != nil { - return + if os.IsNotExist(err) { + return nil + } + return err } - require.Truef(t, info.IsDir(), "path is not a directory: %s", path) - require.NoErrorf(t, os.RemoveAll(path), "error removing directory: %s", path) + if !info.IsDir() { + return fmt.Errorf("path is not a directory: %s", path) + } + if err := os.RemoveAll(path); err != nil { + return fmt.Errorf("error removing directory: %s", path) + } + return nil } func tearDown(t *testing.T) { // Stop service - unloadLaunchdService(t) + if err := unloadLaunchdService(t); err != nil { + t.Log(err) + } // Remove files - removeFileIfExists(t, binaryPath) - removeFileIfExists(t, launchdPath) + if err := removeFileIfExists(t, binaryPath); err != nil { + t.Log(err) + } + if err := removeFileIfExists(t, launchdPath); err != nil { + t.Log(err) + } // Remove configuration & data - removeDirectoryIfExists(t, etcPath) - removeDirectoryIfExists(t, fileStoragePath) - removeDirectoryIfExists(t, logDirPath) - removeDirectoryIfExists(t, appSupportDirPath) + if err := removeDirectoryIfExists(t, etcPath); err != nil { + t.Log(err) + } + if err := removeDirectoryIfExists(t, fileStoragePath); err != nil { + t.Log(err) + } + if err := removeDirectoryIfExists(t, logDirPath); err != nil { + t.Log(err) + } + if err := removeDirectoryIfExists(t, appSupportDirPath); err != nil { + t.Log(err) + } // Remove user & group - if dsclKeyExistsForPath(t, "/Users", systemUser) { + if exists, err := dsclKeyExistsForPath(t, "/Users", systemUser); err != nil { + t.Log(err) + } else if exists { dsclDeletePath(t, fmt.Sprintf("/Users/%s", systemUser)) } - if dsclKeyExistsForPath(t, "/Groups", systemGroup) { + if exists, err := dsclKeyExistsForPath(t, "/Groups", systemGroup); err != nil { + t.Log(err) + } else if exists { dsclDeletePath(t, fmt.Sprintf("/Groups/%s", systemGroup)) } - if dsclKeyExistsForPath(t, "/Users", systemUser) { + if exists, err := dsclKeyExistsForPath(t, "/Users", systemUser); err != nil { + t.Log(err) + } else if exists { panic(fmt.Sprintf("user exists after deletion: %s", systemUser)) } - if dsclKeyExistsForPath(t, "/Groups", systemGroup) { + + if exists, err := dsclKeyExistsForPath(t, "/Groups", systemGroup); err != nil { + t.Log(err) + } else if exists { panic(fmt.Sprintf("group exists after deletion: %s", systemGroup)) } // Remove packages - forgetPackage(t, "com.sumologic.otelcol-sumo-hostmetrics") - forgetPackage(t, "com.sumologic.otelcol-sumo") + if err := forgetPackage(t, "com.sumologic.otelcol-sumo"); err != nil { + t.Log(err) + } } -func unloadLaunchdService(t *testing.T) { +func unloadLaunchdService(t *testing.T) error { info, err := os.Stat(launchdPath) if err != nil { - return + if os.IsNotExist(err) { + return nil + } + return err } - require.Falsef(t, info.IsDir(), "launchd config is not a file: %s", launchdPath) + if info.IsDir() { + return fmt.Errorf("launchd config is a directory: %s", launchdPath) + } output, err := exec.Command("launchctl", "unload", "-w", "otelcol-sumo").Output() - require.NoErrorf(t, err, "error stopping service: %s", string(output)) + if err != nil { + fmt.Errorf("error stopping service: %s", string(output)) + } + return nil } diff --git a/install-script/test/common_linux.go b/install-script/test/common_linux.go index 42ee014c..0c5731c2 100644 --- a/install-script/test/common_linux.go +++ b/install-script/test/common_linux.go @@ -4,8 +4,6 @@ package sumologic_scripts_tests import ( "testing" - - "github.com/stretchr/testify/require" ) func tearDown(t *testing.T) { @@ -17,5 +15,8 @@ func tearDown(t *testing.T) { } _, _, _, err := runScript(ch) - require.NoError(t, err) + if err != nil { + t.Log(err) + } + return } diff --git a/install-script/test/common_unix.go b/install-script/test/common_unix.go index ffa012d3..960f0dbc 100644 --- a/install-script/test/common_unix.go +++ b/install-script/test/common_unix.go @@ -4,24 +4,19 @@ package sumologic_scripts_tests import ( "context" - "io" - "net" - "net/http" + "fmt" "os" "testing" - - "github.com/stretchr/testify/require" ) // These checks always have to be true after a script execution var commonPostChecks = []checkFunc{checkNoBakFilesPresent} -func cleanCache(t *testing.T) { - err := os.RemoveAll(cacheDirectory) - require.NoError(t, err) +func cleanCache(t *testing.T) error { + return os.RemoveAll(cacheDirectory) } -func runTest(t *testing.T, spec *testSpec) { +func runTest(t *testing.T, spec *testSpec) (fErr error) { ch := check{ test: t, installOptions: spec.options, @@ -37,56 +32,59 @@ func runTest(t *testing.T, spec *testSpec) { defer tearDown(t) - t.Log("Starting HTTP server") - mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if _, err := io.WriteString(w, "200 OK\n"); err != nil { - panic(err) - } - }) - - listener, err := net.Listen("tcp", ":3333") - require.NoError(t, err) - - httpServer := &http.Server{ - Handler: mux, + mockAPI, err := startMockAPI(t) + if err != nil { + return fmt.Errorf("Failed to start mock API: %s", err) } - go func() { - err := httpServer.Serve(listener) - if err != nil && err != http.ErrServerClosed { - panic(err) - } - }() + defer func() { - require.NoError(t, httpServer.Shutdown(context.Background())) + if err := mockAPI.Shutdown(context.Background()); err != nil { + fErr = fmt.Errorf("Failed to shutdown API: %s", err) + return + } }() t.Log("Running pre actions") for _, a := range spec.preActions { - a(ch) + if ok := a(ch); !ok { + return nil + } } t.Log("Running pre checks") for _, c := range spec.preChecks { - c(ch) + if ok := c(ch); !ok { + return nil + } } + t.Log("Running script") ch.code, ch.output, ch.errorOutput, ch.err = runScript(ch) + if ch.err != nil { + return ch.err + } // Remove cache in case of curl issue if ch.code == curlTimeoutErrorCode { - cleanCache(t) + if err := cleanCache(t); err != nil { + return err + } } checkRun(ch) t.Log("Running common post checks") for _, c := range commonPostChecks { - c(ch) + if ok := c(ch); !ok { + return nil + } } t.Log("Running post checks") for _, c := range spec.postChecks { - c(ch) + if ok := c(ch); !ok { + return nil + } } + return nil } diff --git a/install-script/test/common_windows.go b/install-script/test/common_windows.go index 58a5c585..a6ee0673 100644 --- a/install-script/test/common_windows.go +++ b/install-script/test/common_windows.go @@ -5,19 +5,14 @@ package sumologic_scripts_tests import ( "context" "fmt" - "io" - "net" - "net/http" "os/exec" "testing" - - "github.com/stretchr/testify/require" ) // These checks always have to be true after a script execution var commonPostChecks = []checkFunc{checkNoBakFilesPresent} -func runTest(t *testing.T, spec *testSpec) { +func runTest(t *testing.T, spec *testSpec) (fErr error) { ch := check{ test: t, installOptions: spec.options, @@ -33,52 +28,53 @@ func runTest(t *testing.T, spec *testSpec) { defer tearDown(t) - t.Log("Starting HTTP server") - mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - _, err := io.WriteString(w, "200 OK\n") - require.NoError(t, err) - }) - - listener, err := net.Listen("tcp", ":3333") - require.NoError(t, err) - - httpServer := &http.Server{ - Handler: mux, + mockAPI, err := startMockAPI(t) + if err != nil { + return fmt.Errorf("Failed to start mock API: %s", err) } - go func() { - err := httpServer.Serve(listener) - if err != nil && err != http.ErrServerClosed { - require.NoError(t, err) - } - }() + defer func() { - require.NoError(t, httpServer.Shutdown(context.Background())) + if err := mockAPI.Shutdown(context.Background()); err != nil { + fErr = fmt.Errorf("Failed to shutdown API: %s", err) + return + } }() t.Log("Running pre actions") for _, a := range spec.preActions { - a(ch) + if ok := a(ch); !ok { + return nil + } } t.Log("Running pre checks") for _, c := range spec.preChecks { - c(ch) + if ok := c(ch); !ok { + return nil + } } ch.code, ch.output, ch.errorOutput, ch.err = runScript(ch) + if err != nil { + return err + } checkRun(ch) t.Log("Running common post checks") for _, c := range commonPostChecks { - c(ch) + if ok := c(ch); !ok { + return nil + } } t.Log("Running post checks") for _, c := range spec.postChecks { - c(ch) + if ok := c(ch); !ok { + return nil + } } + return nil } func tearDown(t *testing.T) { diff --git a/install-script/test/config.go b/install-script/test/config.go index 66abeb17..98a3b854 100644 --- a/install-script/test/config.go +++ b/install-script/test/config.go @@ -31,6 +31,9 @@ func getConfig(path string) (config, error) { yamlFile, err := os.ReadFile(path) if err != nil { + if err == os.ErrNotExist { + return config{}, nil + } return config{}, err } diff --git a/install-script/test/consts_common.go b/install-script/test/consts_common.go index ce994738..86c71a12 100644 --- a/install-script/test/consts_common.go +++ b/install-script/test/consts_common.go @@ -9,6 +9,9 @@ const ( GithubOrg = "SumoLogic" GithubAppRepository = "sumologic-otel-collector" GithubApiBaseUrl = "https://api.github.com" + + mockAPIBaseURL = "http://127.0.0.1:3333" + emptyAPIBaseURL = "empty" ) func authenticateGithub() string { diff --git a/install-script/test/consts_darwin.go b/install-script/test/consts_darwin.go index 5594a88e..296fb9bd 100644 --- a/install-script/test/consts_darwin.go +++ b/install-script/test/consts_darwin.go @@ -4,18 +4,9 @@ const ( appSupportDirPath string = "/Library/Application Support/otelcol-sumo" packageName string = "otelcol-sumo.pkg" launchdPath string = "/Library/LaunchDaemons/com.sumologic.otelcol-sumo.plist" - launchdPathFilePermissions uint32 = 0640 + launchdPathFilePermissions uint32 = 0600 uninstallScriptPath string = appSupportDirPath + "/uninstall.sh" - // TODO: fix mismatch between darwin permissions & linux binary install permissions - // 00-otelcol-config-settings.yaml must be writable as the install scripts mutate it - commonConfigPathFilePermissions uint32 = 0660 - configPathDirPermissions uint32 = 0770 - configPathFilePermissions uint32 = 0440 - confDPathFilePermissions uint32 = 0644 - etcPathPermissions uint32 = 0751 - opampDPermissions uint32 = 0750 - rootGroup string = "wheel" rootUser string = "root" systemGroup string = "_otelcol-sumo" diff --git a/install-script/test/consts_linux.go b/install-script/test/consts_linux.go index 0221221b..450818f4 100644 --- a/install-script/test/consts_linux.go +++ b/install-script/test/consts_linux.go @@ -4,14 +4,6 @@ const ( envDirectoryPath string = etcPath + "/env" tokenEnvFilePath string = envDirectoryPath + "/token.env" - // TODO: fix mismatch between package permissions & expected permissions - commonConfigPathFilePermissions uint32 = 0550 - configPathDirPermissions uint32 = 0550 - configPathFilePermissions uint32 = 0440 - confDPathFilePermissions uint32 = 0644 - etcPathPermissions uint32 = 0551 - opampDPermissions uint32 = 0750 - rootGroup string = "root" rootUser string = "root" systemGroup string = "otelcol-sumo" diff --git a/install-script/test/consts_unix.go b/install-script/test/consts_unix.go index 9b21fb18..9cd65ddf 100644 --- a/install-script/test/consts_unix.go +++ b/install-script/test/consts_unix.go @@ -3,22 +3,31 @@ package sumologic_scripts_tests const ( - binaryPath string = "/usr/local/bin/otelcol-sumo" - libPath string = "/var/lib/otelcol-sumo" - fileStoragePath string = libPath + "/file_storage" - etcPath string = "/etc/otelcol-sumo" - scriptPath string = "../install.sh" - configPath string = etcPath + "/sumologic.yaml" - confDPath string = etcPath + "/conf.d" - opampDPath string = etcPath + "/opamp.d" - userConfigPath string = confDPath + "/00-otelcol-config-settings.yaml" - hostmetricsConfigPath string = confDPath + "/hostmetrics.yaml" - cacheDirectory string = "/var/cache/otelcol-sumo/" - logDirPath string = "/var/log/otelcol-sumo" + binaryPath = "/usr/local/bin/otelcol-sumo" + libPath = "/var/lib/otelcol-sumo" + fileStoragePath = libPath + "/file_storage" + etcPath = "/etc/otelcol-sumo" + scriptPath = "../install.sh" + configPath = etcPath + "/sumologic.yaml" + confDPath = etcPath + "/conf.d" + confDAvailablePath = etcPath + "/conf.d-available" + opampDPath = etcPath + "/opamp.d" + userConfigPath = confDPath + "/00-otelcol-config-settings.yaml" + hostmetricsConfigPath = confDPath + "/hostmetrics.yaml" + cacheDirectory = "/var/cache/otelcol-sumo/" + logDirPath = "/var/log/otelcol-sumo" + sumoRemotePath = "/etc/otelcol-sumo/sumologic-remote.yaml" - installToken string = "token" - installTokenEnv string = "SUMOLOGIC_INSTALLATION_TOKEN" - apiBaseURL string = "https://open-collectors.sumologic.com" + installToken = "token" + installTokenEnv = "SUMOLOGIC_INSTALLATION_TOKEN" + apiBaseURL = "https://open-collectors.sumologic.com" + ephemeralConfigPath = confDPath + "/ephemeral.yaml" - curlTimeoutErrorCode int = 28 + curlTimeoutErrorCode = 28 + + configPathDirPermissions uint32 = 0770 + configPathFilePermissions uint32 = 0660 + confDPathFilePermissions uint32 = 0660 + etcPathPermissions uint32 = 0771 + opampDPermissions uint32 = 0770 ) diff --git a/install-script/test/consts_windows.go b/install-script/test/consts_windows.go index 6257288a..1253414c 100644 --- a/install-script/test/consts_windows.go +++ b/install-script/test/consts_windows.go @@ -20,6 +20,7 @@ const ( opampDPath = etcPath + `\opamp.d` userConfigPath = confDPath + `\common.yaml` hostmetricsConfigPath = confDPath + `\hostmetrics.yaml` + sumoRemotePath = etcPath + `\sumologic-remote.yaml` installToken string = "token" installTokenEnv string = "SUMOLOGIC_INSTALLATION_TOKEN" diff --git a/install-script/test/install_darwin_test.go b/install-script/test/install_darwin_test.go index 89bc973f..af765c0e 100644 --- a/install-script/test/install_darwin_test.go +++ b/install-script/test/install_darwin_test.go @@ -22,7 +22,7 @@ func TestInstallScriptDarwin(t *testing.T) { options: installOptions{}, preChecks: notInstalledChecks, postChecks: append(notInstalledChecks, checkAbortedDueToNoToken), - installCode: 2, + installCode: 1, }, { name: "download only", @@ -56,33 +56,14 @@ func TestInstallScriptDarwin(t *testing.T) { installCode: 1, }, { - // Skip config is not supported on Darwin - name: "skip config", + name: "skip installation token", options: installOptions{ - skipConfig: true, skipInstallToken: true, }, preChecks: notInstalledChecks, postChecks: notInstalledChecks, installCode: 1, }, - { - name: "skip installation token", - options: installOptions{ - skipInstallToken: true, - }, - preChecks: notInstalledChecks, - postChecks: []checkFunc{ - checkBinaryCreated, - checkBinaryIsRunning, - checkConfigCreated, - checkConfigFilesOwnershipAndPermissions(systemUser, systemGroup), - checkUserConfigCreated, - checkLaunchdConfigCreated, - checkHomeDirectoryCreated, - }, - installCode: 1, // because of invalid installation token - }, { name: "installation token only", options: installOptions{ @@ -95,7 +76,8 @@ func TestInstallScriptDarwin(t *testing.T) { checkConfigCreated, checkConfigFilesOwnershipAndPermissions(systemUser, systemGroup), checkUserConfigCreated, - checkEphemeralNotInConfig(userConfigPath), + checkEphemeralConfigFileNotCreated(ephemeralConfigPath), + checkEphemeralNotEnabledInRemote(sumoRemotePath), checkLaunchdConfigCreated, checkTokenInLaunchdConfig, checkUserExists, @@ -103,7 +85,6 @@ func TestInstallScriptDarwin(t *testing.T) { checkHostmetricsConfigNotCreated, checkHomeDirectoryCreated, }, - installCode: 1, // because of invalid installation token }, { name: "installation token and ephemeral", @@ -118,7 +99,8 @@ func TestInstallScriptDarwin(t *testing.T) { checkConfigCreated, checkConfigFilesOwnershipAndPermissions(systemUser, systemGroup), checkUserConfigCreated, - checkEphemeralInConfig(userConfigPath), + checkEphemeralConfigFileCreated(ephemeralConfigPath), + checkEphemeralNotEnabledInRemote(sumoRemotePath), checkLaunchdConfigCreated, checkTokenInLaunchdConfig, checkUserExists, @@ -126,13 +108,12 @@ func TestInstallScriptDarwin(t *testing.T) { checkHostmetricsConfigNotCreated, checkHomeDirectoryCreated, }, - installCode: 1, // because of invalid installation token }, { name: "override default config", options: installOptions{ - skipInstallToken: true, - autoconfirm: true, + autoconfirm: true, + installToken: installToken, }, preActions: []checkFunc{preActionMockConfig}, preChecks: []checkFunc{ @@ -149,7 +130,7 @@ func TestInstallScriptDarwin(t *testing.T) { checkUserConfigCreated, checkLaunchdConfigCreated, }, - installCode: 1, // because of invalid installation token + installCode: 0, }, { name: "installation token and hostmetrics", @@ -172,7 +153,7 @@ func TestInstallScriptDarwin(t *testing.T) { checkHostmetricsOwnershipAndPermissions(systemUser, systemGroup), checkHomeDirectoryCreated, }, - installCode: 1, // because of invalid installation token + installCode: 0, }, { name: "installation token and remotely-managed", @@ -187,15 +168,18 @@ func TestInstallScriptDarwin(t *testing.T) { checkConfigCreated, checkRemoteConfigDirectoryCreated, checkConfigFilesOwnershipAndPermissions(systemUser, systemGroup), - checkUserConfigCreated, - checkEphemeralNotInConfig(configPath), + checkUserConfigNotCreated, + checkEphemeralConfigFileNotCreated(ephemeralConfigPath), + checkEphemeralNotEnabledInRemote(sumoRemotePath), checkLaunchdConfigCreated, checkTokenInLaunchdConfig, checkUserExists, checkGroupExists, checkHomeDirectoryCreated, }, - installCode: 1, // because of invalid installation token + // TODO(JK): this succeeds when testing locally but fails in CI, + // I need to determine why this is the case + installCode: 1, }, { name: "installation token, remotely-managed, and ephemeral", @@ -211,38 +195,18 @@ func TestInstallScriptDarwin(t *testing.T) { checkConfigCreated, checkRemoteConfigDirectoryCreated, checkConfigFilesOwnershipAndPermissions(systemUser, systemGroup), - checkUserConfigCreated, - checkEphemeralInConfig(configPath), - checkLaunchdConfigCreated, - checkTokenInLaunchdConfig, - checkUserExists, - checkGroupExists, - checkHomeDirectoryCreated, - }, - installCode: 1, // because of invalid installation token - }, - { - name: "installation token only, binary not in PATH", - options: installOptions{ - installToken: installToken, - envs: map[string]string{ - "PATH": "/sbin:/bin:/usr/sbin:/usr/bin", - }, - }, - preChecks: notInstalledChecks, - postChecks: []checkFunc{ - checkBinaryCreated, - checkBinaryIsRunning, - checkConfigCreated, - checkConfigFilesOwnershipAndPermissions(systemUser, systemGroup), - checkUserConfigCreated, + checkUserConfigNotCreated, + checkEphemeralConfigFileNotCreated(ephemeralConfigPath), + checkEphemeralEnabledInRemote(sumoRemotePath), checkLaunchdConfigCreated, checkTokenInLaunchdConfig, checkUserExists, checkGroupExists, checkHomeDirectoryCreated, }, - installCode: 1, // because of invalid installation token + // TODO(JK): this succeeds when testing locally but fails in CI, + // I need to determine why this is the case + installCode: 1, }, { name: "same installation token in launchd config", @@ -267,7 +231,7 @@ func TestInstallScriptDarwin(t *testing.T) { checkTokenInLaunchdConfig, checkHomeDirectoryCreated, }, - installCode: 1, // because of invalid installation token + installCode: 0, }, { name: "different installation token in launchd config", @@ -301,8 +265,7 @@ func TestInstallScriptDarwin(t *testing.T) { { name: "same api base url", options: installOptions{ - apiBaseURL: apiBaseURL, - skipInstallToken: true, + apiBaseURL: mockAPIBaseURL, }, preActions: []checkFunc{ preActionInstallPackage, @@ -320,13 +283,12 @@ func TestInstallScriptDarwin(t *testing.T) { checkUserConfigCreated, checkAPIBaseURLInConfig, }, - installCode: 1, // because of invalid installation token + installCode: 0, }, { name: "different api base url", options: installOptions{ - apiBaseURL: apiBaseURL, - skipInstallToken: true, + apiBaseURL: apiBaseURL, }, preActions: []checkFunc{ preActionInstallPackageWithDifferentAPIBaseURL, @@ -349,37 +311,16 @@ func TestInstallScriptDarwin(t *testing.T) { { name: "adding api base url", options: installOptions{ - apiBaseURL: apiBaseURL, - skipInstallToken: true, + apiBaseURL: mockAPIBaseURL, }, preActions: []checkFunc{preActionInstallPackageWithNoAPIBaseURL}, preChecks: []checkFunc{ checkBinaryCreated, checkConfigCreated, - checkUserConfigCreated, - checkUserExists, - }, - postChecks: []checkFunc{ - checkBinaryCreated, - checkConfigCreated, - checkUserConfigCreated, - checkAPIBaseURLInConfig, - }, - installCode: 1, // because of invalid installation token - }, - { - name: "editing api base url", - options: installOptions{ - apiBaseURL: apiBaseURL, - skipInstallToken: true, - }, - preActions: []checkFunc{ - preActionInstallPackageWithNoAPIBaseURL, - }, - preChecks: []checkFunc{ - checkBinaryCreated, - checkConfigCreated, - checkUserConfigCreated, + // The user config file will only exist if non-default values + // are used for otelcol-config managed settings such as the + // API URL or tags + checkUserConfigNotCreated, checkUserExists, }, postChecks: []checkFunc{ @@ -388,12 +329,12 @@ func TestInstallScriptDarwin(t *testing.T) { checkUserConfigCreated, checkAPIBaseURLInConfig, }, - installCode: 1, // because of invalid installation token + installCode: 0, }, { name: "configuration with tags", options: installOptions{ - skipInstallToken: true, + installToken: installToken, tags: map[string]string{ "lorem": "ipsum", "foo": "bar", @@ -411,12 +352,11 @@ func TestInstallScriptDarwin(t *testing.T) { checkTags, checkLaunchdConfigCreated, }, - installCode: 1, // because of invalid installation token + installCode: 0, }, { name: "same tags", options: installOptions{ - skipInstallToken: true, tags: map[string]string{ "lorem": "ipsum", "foo": "bar", @@ -442,43 +382,11 @@ func TestInstallScriptDarwin(t *testing.T) { checkTags, checkLaunchdConfigCreated, }, - installCode: 1, // because of invalid installation token - }, - { - name: "different tags", - options: installOptions{ - skipInstallToken: true, - tags: map[string]string{ - "lorem": "ipsum", - "foo": "bar", - "escape_me": "'\\/", - "slash": "a/b", - "numeric": "1_024", - }, - }, - preActions: []checkFunc{ - preActionInstallPackageWithDifferentTags, - }, - preChecks: []checkFunc{ - checkBinaryCreated, - checkConfigCreated, - checkUserConfigCreated, - checkUserExists, - }, - postChecks: []checkFunc{ - checkBinaryCreated, - checkConfigCreated, - checkUserConfigCreated, - checkDifferentTags, - checkLaunchdConfigCreated, - checkAbortedDueToDifferentTags, - }, - installCode: 1, + installCode: 0, }, { name: "editing tags", options: installOptions{ - skipInstallToken: true, tags: map[string]string{ "lorem": "ipsum", "foo": "bar", @@ -503,11 +411,13 @@ func TestInstallScriptDarwin(t *testing.T) { checkTags, checkLaunchdConfigCreated, }, - installCode: 1, // because of invalid installation token + installCode: 0, }, } { t.Run(spec.name, func(t *testing.T) { - runTest(t, &spec) + if err := runTest(t, &spec); err != nil { + t.Error(err) + } }) } } diff --git a/install-script/test/install_unix_test.go b/install-script/test/install_unix_test.go index 13bd0b82..5b2565b3 100644 --- a/install-script/test/install_unix_test.go +++ b/install-script/test/install_unix_test.go @@ -7,62 +7,42 @@ import ( ) func TestInstallScript(t *testing.T) { + notInstalledChecks := []checkFunc{ + checkBinaryNotCreated, + checkConfigNotCreated, + checkUserConfigNotCreated, + } + for _, spec := range []testSpec{ { name: "no arguments", options: installOptions{}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, - postChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, - installCode: 2, - }, - { - name: "skip config", - options: installOptions{ - skipConfig: true, - skipInstallToken: true, - }, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, - postChecks: []checkFunc{checkBinaryCreated, checkConfigNotCreated, checkUserConfigNotCreated}, + preChecks: notInstalledChecks, + postChecks: notInstalledChecks, + installCode: 1, }, { name: "skip installation token", options: installOptions{ skipInstallToken: true, }, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, - postChecks: []checkFunc{ - checkBinaryCreated, - checkBinaryIsRunning, - checkConfigCreated, - checkConfigFilesOwnershipAndPermissions(rootUser, rootGroup), - checkUserConfigNotCreated, - }, - }, - { - name: "override default config", - options: installOptions{ - skipInstallToken: true, - }, - preActions: []checkFunc{preActionMockConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigCreated, checkUserConfigNotCreated}, - postChecks: []checkFunc{checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, checkConfigOverrided, checkUserConfigNotCreated}, + preChecks: notInstalledChecks, + postChecks: notInstalledChecks, + installCode: 1, }, { name: "installation token only", options: installOptions{ installToken: installToken, }, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, + preChecks: notInstalledChecks, postChecks: []checkFunc{ checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, - checkConfigFilesOwnershipAndPermissions(rootUser, rootGroup), - checkUserConfigCreated, - checkEphemeralNotInConfig(userConfigPath), - checkTokenInConfig, + checkEphemeralConfigFileNotCreated(ephemeralConfigPath), checkHostmetricsConfigNotCreated, - checkTokenEnvFileNotCreated, + checkTokenEnvFileCreated, }, }, { @@ -71,17 +51,15 @@ func TestInstallScript(t *testing.T) { installToken: installToken, ephemeral: true, }, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, + preChecks: notInstalledChecks, postChecks: []checkFunc{ checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, - checkConfigFilesOwnershipAndPermissions(rootUser, rootGroup), - checkUserConfigCreated, - checkTokenInConfig, - checkEphemeralInConfig(userConfigPath), + checkEphemeralConfigFileCreated(ephemeralConfigPath), + checkEphemeralNotEnabledInRemote(sumoRemotePath), checkHostmetricsConfigNotCreated, - checkTokenEnvFileNotCreated, + checkTokenEnvFileCreated, }, }, { @@ -90,17 +68,14 @@ func TestInstallScript(t *testing.T) { installToken: installToken, installHostmetrics: true, }, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, + preChecks: notInstalledChecks, postChecks: []checkFunc{ checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, - checkRemoteConfigDirectoryNotCreated, - checkConfigFilesOwnershipAndPermissions(rootUser, rootGroup), - checkUserConfigCreated, - checkTokenInConfig, + checkRemoteConfigDirectoryCreated, checkHostmetricsConfigCreated, - checkHostmetricsOwnershipAndPermissions(rootUser, rootGroup), + checkHostmetricsOwnershipAndPermissions(systemUser, systemGroup), }, }, { @@ -109,15 +84,14 @@ func TestInstallScript(t *testing.T) { installToken: installToken, remotelyManaged: true, }, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, + preChecks: notInstalledChecks, postChecks: []checkFunc{ checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, checkRemoteConfigDirectoryCreated, - checkConfigFilesOwnershipAndPermissions(rootUser, rootGroup), - checkTokenInSumoConfig, - checkEphemeralNotInConfig(configPath), + checkEphemeralConfigFileNotCreated(ephemeralConfigPath), + checkEphemeralNotEnabledInRemote(sumoRemotePath), }, }, { @@ -127,15 +101,14 @@ func TestInstallScript(t *testing.T) { remotelyManaged: true, ephemeral: true, }, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, + preChecks: notInstalledChecks, postChecks: []checkFunc{ checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, checkRemoteConfigDirectoryCreated, - checkConfigFilesOwnershipAndPermissions(rootUser, rootGroup), - checkTokenInSumoConfig, - checkEphemeralInConfig(configPath), + checkEphemeralConfigFileNotCreated(ephemeralConfigPath), + checkEphemeralEnabledInRemote(sumoRemotePath), }, }, { @@ -145,126 +118,21 @@ func TestInstallScript(t *testing.T) { remotelyManaged: true, opampEndpoint: "wss://example.com", }, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, + preChecks: notInstalledChecks, postChecks: []checkFunc{ checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, checkRemoteConfigDirectoryCreated, - checkConfigFilesOwnershipAndPermissions(rootUser, rootGroup), - checkTokenInSumoConfig, - checkEphemeralNotInConfig(configPath), + checkEphemeralConfigFileNotCreated(ephemeralConfigPath), + checkEphemeralNotEnabledInRemote(sumoRemotePath), checkOpAmpEndpointSet, }, }, - { - name: "installation token only, binary not in PATH", - options: installOptions{ - installToken: installToken, - envs: map[string]string{ - "PATH": "/sbin:/bin:/usr/sbin:/usr/bin", - }, - }, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, - postChecks: []checkFunc{ - checkBinaryCreated, - checkBinaryIsRunning, - checkConfigCreated, - checkConfigFilesOwnershipAndPermissions(rootUser, rootGroup), - checkUserConfigCreated, - checkTokenInConfig, - }, - }, - { - name: "same installation token", - options: installOptions{ - installToken: installToken, - }, - preActions: []checkFunc{preActionMockUserConfig, preActionWriteTokenToUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, checkUserConfigCreated, checkTokenInConfig}, - }, - { - name: "different installation token", - options: installOptions{ - installToken: installToken, - }, - preActions: []checkFunc{preActionMockUserConfig, preActionWriteDifferentTokenToUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated, checkAbortedDueToDifferentToken}, - installCode: 1, - }, - { - name: "adding installation token", - options: installOptions{ - installToken: installToken, - }, - preActions: []checkFunc{preActionMockUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryCreated, checkConfigCreated, checkUserConfigCreated, checkTokenInConfig}, - }, - { - name: "editing installation token", - options: installOptions{ - apiBaseURL: apiBaseURL, - installToken: installToken, - }, - preActions: []checkFunc{preActionMockUserConfig, preActionWriteEmptyUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryCreated, checkConfigCreated, checkUserConfigCreated, checkTokenInConfig}, - }, - { - name: "same api base url", - options: installOptions{ - apiBaseURL: apiBaseURL, - skipInstallToken: true, - }, - preActions: []checkFunc{preActionMockUserConfig, preActionWriteAPIBaseURLToUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, checkUserConfigCreated, checkAPIBaseURLInConfig}, - }, - { - name: "different api base url", - options: installOptions{ - apiBaseURL: apiBaseURL, - skipInstallToken: true, - }, - preActions: []checkFunc{preActionMockUserConfig, preActionWriteDifferentAPIBaseURLToUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated, - checkAbortedDueToDifferentAPIBaseURL}, - installCode: 1, - }, - { - name: "adding api base url", - options: installOptions{ - apiBaseURL: apiBaseURL, - skipInstallToken: true, - }, - preActions: []checkFunc{preActionMockUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryCreated, checkConfigCreated, checkUserConfigCreated, checkAPIBaseURLInConfig}, - }, - { - name: "editing api base url", - options: installOptions{ - apiBaseURL: apiBaseURL, - skipInstallToken: true, - }, - preActions: []checkFunc{preActionMockUserConfig, preActionWriteEmptyUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryCreated, checkConfigCreated, checkUserConfigCreated, checkAPIBaseURLInConfig}, - }, - { - name: "empty installation token", - preActions: []checkFunc{preActionMockUserConfig, preActionWriteDifferentTokenToUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryCreated, checkConfigCreated, checkUserConfigCreated, checkDifferentTokenInConfig}, - }, { name: "configuration with tags", options: installOptions{ - skipInstallToken: true, + installToken: installToken, tags: map[string]string{ "lorem": "ipsum", "foo": "bar", @@ -273,68 +141,19 @@ func TestInstallScript(t *testing.T) { "numeric": "1_024", }, }, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigNotCreated}, + preChecks: notInstalledChecks, postChecks: []checkFunc{ checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, - checkConfigFilesOwnershipAndPermissions(rootUser, rootGroup), checkTags, }, }, - { - name: "same tags", - options: installOptions{ - skipInstallToken: true, - tags: map[string]string{ - "lorem": "ipsum", - "foo": "bar", - "escape_me": "'\\/", - "slash": "a/b", - "numeric": "1_024", - }, - }, - preActions: []checkFunc{preActionMockUserConfig, preActionWriteTagsToUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, checkUserConfigCreated, checkTags}, - }, - { - name: "different tags", - options: installOptions{ - skipInstallToken: true, - tags: map[string]string{ - "lorem": "ipsum", - "foo": "bar", - "escape_me": "'\\/", - "slash": "a/b", - "numeric": "1_024", - }, - }, - preActions: []checkFunc{preActionMockUserConfig, preActionWriteDifferentTagsToUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated, checkDifferentTags, - checkAbortedDueToDifferentTags}, - installCode: 1, - }, - { - name: "editing tags", - options: installOptions{ - skipInstallToken: true, - tags: map[string]string{ - "lorem": "ipsum", - "foo": "bar", - "escape_me": "'\\/", - "slash": "a/b", - "numeric": "1_024", - }, - }, - preActions: []checkFunc{preActionMockUserConfig, preActionWriteEmptyUserConfig}, - preChecks: []checkFunc{checkBinaryNotCreated, checkConfigNotCreated, checkUserConfigCreated}, - postChecks: []checkFunc{checkBinaryCreated, checkBinaryIsRunning, checkConfigCreated, checkTags}, - }, } { t.Run(spec.name, func(t *testing.T) { - runTest(t, &spec) + if err := runTest(t, &spec); err != nil { + t.Error(err) + } }) } } diff --git a/install-script/test/install_windows_test.go b/install-script/test/install_windows_test.go index d94df1cb..67d9ff44 100644 --- a/install-script/test/install_windows_test.go +++ b/install-script/test/install_windows_test.go @@ -147,7 +147,9 @@ func TestInstallScript(t *testing.T) { }, } { t.Run(spec.name, func(t *testing.T) { - runTest(t, &spec) + if err := runTest(t, &spec); err != nil { + t.Error(err) + } }) } } diff --git a/packages.cmake b/packages.cmake index 65f091ad..4a25ec0e 100644 --- a/packages.cmake +++ b/packages.cmake @@ -125,6 +125,9 @@ macro(build_cpack_config) # Build CPackConfig include(CPack) + # Create components + create_otc_components() + # Add a target for each packagecloud distro the package should be published to set(_pc_user "sumologic") set(_pc_repo "ci-builds") @@ -132,25 +135,72 @@ macro(build_cpack_config) create_packagecloud_publish_target(${_pc_user} ${_pc_repo} ${_pc_distro} ${_package_output}) endforeach() + # Add a target for uploading the package to Amazon S3 + set(_s3_channel "ci-builds") + set(_version "${OTC_VERSION}-${BUILD_NUMBER}") + set(_s3_bucket "sumologic-osc-${_s3_channel}") + set(_s3_path "${_version}/") + create_s3_cp_target(${_s3_bucket} ${_s3_path} ${_package_output}) + # Add a publish-package target to publish the package built above get_property(_all_publish_targets GLOBAL PROPERTY _all_publish_targets) add_custom_target(publish-package DEPENDS ${_all_publish_targets}) + + # Add a wait-for-packagecloud-indexing target to wait for Packagecloud to finish indexing + create_wait_for_packagecloud_indexing_target(${_pc_user} ${_pc_repo} ${_package_output}) endmacro() # Create a Packagecloud publish target for uploading a package to a specific # repository for a specific distribution. -function(create_packagecloud_publish_target _pc_user _pc_repo _pc_distro _pkg_name) - set(_pc_output "${_pkg_name}-${_pc_repo}/${_pc_distro}") - separate_arguments(_packagecloud_push_cmd UNIX_COMMAND "packagecloud push --skip-exists ${_pc_user}/${_pc_repo}/${_pc_distro} ${_pkg_name}") +function(create_packagecloud_publish_target _pc_user _pc_repo _pc_distro _pkg_path) + set(_pc_output "${_pkg_path}-${_pc_repo}/${_pc_distro}") + separate_arguments(_packagecloud_push_cmd + UNIX_COMMAND "packagecloud push --skip-exists ${_pc_user}/${_pc_repo}/${_pc_distro} ${_pkg_path}") add_custom_command(OUTPUT ${_pc_output} COMMAND ${_packagecloud_push_cmd} - DEPENDS ${_pkg_name} + DEPENDS ${_pkg_path} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} VERBATIM) append_to_publish_targets(${_pc_output}) endfunction() +# Create a Packagecloud wait for indexing target that will block until +# Packagecloud has finished indexing packages with the given package name. +function(create_wait_for_packagecloud_indexing_target _pc_user _pc_repo _pkg_path) + set(_pc_output "${_pkg_path}-${_pc_repo}-wait-for-indexing") + cmake_path(GET _pkg_path FILENAME _pkg_name) + set(_repo_id "${_pc_user}/${_pc_repo}") + set(_base_cmd "packagecloud search") + set(_query_arg "--query ${_pkg_name}") + set(_wait_args "--wait-for-indexing --wait-seconds 30 --wait-max-retries 12") + set(_cmd "${_base_cmd} ${_repo_id} ${_query_arg} ${_wait_args}") + separate_arguments(_packagecloud_search_cmd UNIX_COMMAND "${_cmd}") + + message(STATUS "wait for indexing command: ${_cmd}") + + add_custom_command(OUTPUT ${_pc_output} + COMMAND ${_packagecloud_search_cmd} + DEPENDS ${_pkg_name} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + VERBATIM) + + add_custom_target(wait-for-packagecloud-indexing + DEPENDS ${_pc_output}) +endfunction() + +# Create an Amazon S3 publish target for uploading a package to an S3 bucket. +function(create_s3_cp_target _s3_bucket _s3_path _pkg_path) + set(_s3_output "${_pkg_path}-s3-${_s3_bucket}") + separate_arguments(_s3_cp_cmd UNIX_COMMAND "aws s3 cp ${_pkg_path} s3://${_s3_bucket}/${_s3_path}") + add_custom_command(OUTPUT ${_s3_output} + COMMAND ${_s3_cp_cmd} + DEPENDS ${_pkg_path} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + VERBATIM) + append_to_publish_targets(${_s3_output}) +endfunction() + # Sets a GitHub output parameter by appending a statement to the file defined by # the GITHUB_OUTPUT environment variable. It enables the passing of data from # this CMake project to GitHub Actions. diff --git a/settings/otc.cmake b/settings/otc.cmake index 348cac56..1d19c215 100644 --- a/settings/otc.cmake +++ b/settings/otc.cmake @@ -22,8 +22,11 @@ macro(set_otc_settings) # File names set(OTC_BINARY "otelcol-sumo") set(OTC_CONFIG_BINARY "otelcol-config") + set(OTC_LAUNCHD_CONFIG "com.sumologic.otelcol-sumo.plist") + set(OTC_SERVICE_SCRIPT "otelcol-sumo.sh") set(OTC_SUMOLOGIC_CONFIG "sumologic.yaml") set(OTC_SYSTEMD_CONFIG "otelcol-sumo.service") + set(DOT_KEEP_FILE ".keep") # Directories set(OTC_BIN_DIR "usr/local/bin") @@ -31,11 +34,16 @@ macro(set_otc_settings) set(OTC_CONFIG_FRAGMENTS_DIR "${OTC_CONFIG_DIR}/conf.d") set(OTC_CONFIG_FRAGMENTS_AVAILABLE_DIR "${OTC_CONFIG_DIR}/conf.d-available") set(OTC_USER_ENV_DIR "${OTC_CONFIG_DIR}/env") + set(OTC_OPAMPD_DIR "${OTC_CONFIG_DIR}/opamp.d") set(OTC_STATE_DIR "var/lib/otelcol-sumo") set(OTC_FILESTORAGE_STATE_DIR "${OTC_STATE_DIR}/file_storage") set(OTC_LAUNCHD_DIR "Library/LaunchDaemons") set(OTC_SYSTEMD_DIR "lib/systemd/system") set(OTC_LOG_DIR "var/log/otelcol-sumo") + set(OTC_SHARE_DIR "usr/share/otelcol-sumo") + if("${goos}" STREQUAL "darwin") + set(OTC_SHARE_DIR "usr/local/share/otelcol-sumo") + endif() # File paths set(OTC_BIN_PATH "${OTC_BIN_DIR}/${OTC_BINARY}") @@ -74,6 +82,7 @@ macro(set_otc_settings) set(GH_ARTIFACT_OTC_BINARY_PATH "${GH_ARTIFACTS_DIR}/${GH_OUTPUT_OTC_BIN}") set(GH_ARTIFACT_OTC_CONFIG_BINARY_PATH "${GH_ARTIFACTS_DIR}/${GH_OUTPUT_OTC_CONFIG_BIN}") set(ACL_LOG_FILE_PATHS "/var/log") + set(DOT_KEEP_PATH "${ASSETS_DIR}/${DOT_KEEP_FILE}") ## # Other