diff --git a/.github/actions/run-sanity-tests/action.yaml b/.github/actions/run-sanity-and-linter-tests/action.yaml similarity index 79% rename from .github/actions/run-sanity-tests/action.yaml rename to .github/actions/run-sanity-and-linter-tests/action.yaml index fe0f158..f3d9d97 100644 --- a/.github/actions/run-sanity-tests/action.yaml +++ b/.github/actions/run-sanity-and-linter-tests/action.yaml @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -name: run-sanity-tests -description: "Runs sanity tests" +name: run-sanity-and-linter-tests +description: "Runs sanity and linter tests" runs: using: composite @@ -25,3 +25,7 @@ runs: - name: "Run sanity test" shell: bash run: pushd ~/.ansible/collections/ansible_collections/dynatrace/oneagent && ansible-test sanity && popd + + - name: "Run linter test" + shell: bash + run: pushd ~/.ansible/collections/ansible_collections/dynatrace/oneagent && ansible-lint && popd diff --git a/.github/actions/setup-build-environment/action.yaml b/.github/actions/setup-build-environment/action.yaml index d93e58b..4a4a8a5 100644 --- a/.github/actions/setup-build-environment/action.yaml +++ b/.github/actions/setup-build-environment/action.yaml @@ -23,4 +23,4 @@ runs: python-version: '3.10' - name: "Install dependencies" shell: bash - run: pip install ansible + run: pip install ansible ansible-lint diff --git a/.github/actions/upload-collection/action.yaml b/.github/actions/upload-collection/action.yaml index 0c7d2c9..79b4c91 100644 --- a/.github/actions/upload-collection/action.yaml +++ b/.github/actions/upload-collection/action.yaml @@ -23,4 +23,4 @@ runs: with: name: dynatrace-oneagent-${{ github.sha }} retention-days: 7 - path: dynatrace-oneagent* \ No newline at end of file + path: dynatrace-oneagent* diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index a0cce95..7da6aa4 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -30,6 +30,6 @@ jobs: - name: Build the collection uses: ./.github/actions/build-collection - name: Run sanity tests - uses: ./.github/actions/run-sanity-tests + uses: ./.github/actions/run-sanity-and-linter-tests - name: Run component tests uses: ./.github/actions/run-component-tests diff --git a/.github/workflows/build-test-and-publish.yaml b/.github/workflows/build-test-and-publish.yaml index bfb1f15..c301800 100644 --- a/.github/workflows/build-test-and-publish.yaml +++ b/.github/workflows/build-test-and-publish.yaml @@ -16,7 +16,7 @@ name: "Build, test and publish" on: push: tags: - - "v*.*.*" + - "v*.*.*" jobs: build-test-and-publish: @@ -34,7 +34,7 @@ jobs: - name: Build the collection uses: ./.github/actions/build-collection - name: Run sanity tests - uses: ./.github/actions/run-sanity-tests + uses: ./.github/actions/run-sanity-and-linter-tests - name: Run component tests uses: ./.github/actions/run-component-tests - name: Publish to Ansible Galaxy diff --git a/.github/workflows/build-test-and-upload.yaml b/.github/workflows/build-test-and-upload.yaml index 8210585..16be671 100644 --- a/.github/workflows/build-test-and-upload.yaml +++ b/.github/workflows/build-test-and-upload.yaml @@ -15,7 +15,7 @@ name: "Build, test and upload" on: push: - branches: [ "master" ] + branches: ["master"] workflow_dispatch: jobs: @@ -30,7 +30,7 @@ jobs: - name: Build the collection uses: ./.github/actions/build-collection - name: Run sanity tests - uses: ./.github/actions/run-sanity-tests + uses: ./.github/actions/run-sanity-and-linter-tests - name: Run component tests uses: ./.github/actions/run-component-tests - name: Upload the collection diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cdd2c68..33a0ca0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,8 +1,45 @@ --- repos: - repo: https://github.com/ansible/ansible-lint - rev: v24.2.2 + rev: v24.12.2 hooks: - id: ansible-lint pass_filenames: false - args: ['--exclude', 'roles/oneagent/examples', 'tests/component/resources/ansible/oneagent.yml'] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: mixed-line-ending + - id: trailing-whitespace + + - repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + + - repo: local + hooks: + - id: shfmt + name: shfmt + additional_dependencies: [mvdan.cc/sh/v3/cmd/shfmt@v3.8.0] + entry: shfmt + language: golang + exclude: roles/oneagent/tests/resources/installers/Dynatrace-OneAgent-Linux.sh + args: + - "-s" + - "-w" + types: [shell] + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: "v0.10.0.1" + hooks: + - id: shellcheck + exclude: gradlew + args: + - "--external-sources" + + - repo: https://github.com/adrienverge/yamllint + rev: "v1.35.0" + hooks: + - id: yamllint + files: \.(yaml|yml) diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..cb4fd34 --- /dev/null +++ b/.yamllint @@ -0,0 +1,6 @@ +--- +rules: + document-start: + ignore: | + /.github + line-length: disable diff --git a/CHANGELOG.md b/CHANGELOG.md index a502413..4f94559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ All notable changes to this project will be documented in this file. - Removed `oneagent_remove_signature` parameter. - Added ability to configure installation using `oneagentctl`. - Removed the need to provide the required parameters in case of uninstallation. -- Added node restart option. +- Added node restart option. ## [0.3.0] - 2021-02-12 diff --git a/README.md b/README.md index d95ce14..d9773d2 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ configuration of OneAgent and ensures the OneAgent service remains in a running ## Requirements -* Ansible >= 2.15.0 -* pywinrm >= 0.4.3 (Windows only) +- Ansible >= 2.15.0 +- pywinrm >= 0.4.3 (Windows only) ## Installation @@ -44,8 +44,7 @@ broken in the latest version (please report an issue in this repository). Use th ansible-galaxy collection install dynatrace.oneagent:==1.0.0 ``` -See [using Ansible collections](https://docs.ansible.com/ansible/devel/user_guide/collections_using.html) for more -details. +See [using Ansible collections](https://docs.ansible.com/ansible/devel/user_guide/collections_using.html) for more details. ## Use Cases @@ -53,6 +52,8 @@ See [OneAgent role README](roles/oneagent/README.md) for more details. ## Testing +The collection was tested against Ansible sanity tests and component tests. The latter runs regular deployment with +the installer and checks veriety of installation scenarios. See [OneAgent role tests README](roles/oneagent/tests/README.md) for more details. ## Support diff --git a/galaxy.yml b/galaxy.yml index d476328..56e4c5c 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -6,15 +6,20 @@ readme: README.md authors: - Dynatrace LLC description: Module to install and configure Dynatrace OneAgent deployment -license: - - MIT +license_file: LICENSE tags: - dynatrace - oneagent - - monitoring + - agent - deployment + - monitoring + - infrastructure + - linux + - windows repository: https://github.com/Dynatrace/Dynatrace-OneAgent-Ansible documentation: https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/deployment-orchestration/ansible build_ignore: - - roles/oneagent/tests - - roles/oneagent/examples \ No newline at end of file + - .github + - .pre-commit-config.yaml + - .gitignore + - venv diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bb51aeb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ +[tool.pylint.BASIC] +max-line-length = 120 + +[tool.pylint.'MESSAGES CONTROL'] +disable = ''' + missing-docstring, + ''' + +[tool.black] +line-length = 120 diff --git a/roles/oneagent/README.md b/roles/oneagent/README.md index 3f49bab..ea852bd 100644 --- a/roles/oneagent/README.md +++ b/roles/oneagent/README.md @@ -2,20 +2,20 @@ ## Requirements -* OneAgent version 1.199+. -* Script access to the OneAgent installer file. You can either: - * configure the script to download the installer directly from your Dynatrace environment, - * download it yourself and upload it to the primary node. +- OneAgent version 1.199+. +- Script access to the OneAgent installer file. You can either: + - configure the script to download the installer directly from your Dynatrace environment, + - download it yourself and upload it to the primary node. ### Direct download from your environment The script utilizes [Deployment API] to download a platform-specific installer to the target machine. You will need to supply the role with information required to authenticate the API call in your environment: -* The environment URL: - * **SaaS**: `https://{your-environment-id}.live.dynatrace.com` - * **Managed**: `https://{your-domain}/e/{your-environment-id}` -* The [PaaS token] of your environment +- The environment URL: + - **SaaS**: `https://{your-environment-id}.live.dynatrace.com` + - **Managed**: `https://{your-domain}/e/{your-environment-id}` +- The [PaaS token] of your environment ### Local installer @@ -27,10 +27,11 @@ If you don't specify the installer, the script attempts to use the direct downlo The role is capable of configuring existing installation by utilizing `oneagentctl`. There are 2 ways of applying configuration: + - In case the Agent in the same or lower version is installed on specified host already, the script -uses provided installation parameters and runs `oneagentctl` with them, skipping installation procedure. -- In case no `environment_url`, `paas_token` and `local_installer` parameters are provided, -the script runs `oneagentctl` with provided parameters directly. + uses provided installation parameters and runs `oneagentctl` with them, skipping installation procedure. +- In case no `environment_url`, `paas_token` and `local_installer` parameters are provided, + the script runs `oneagentctl` with provided parameters directly. For full list of suitable parameters, see [OneAgent configuration via command-line interface]. @@ -38,34 +39,31 @@ For full list of suitable parameters, see [OneAgent configuration via command-li The following variables are available in `defaults/main/` and can be overridden: -| Name | Default | Description -|-|-|- -| `oneagent_environment_url` | `-` | The URL of the target Dynatrace environment (see [Direct download from your environment](#direct-download-from-your-environment)). -| `oneagent_paas_token` | `-` | The [PaaS Token] retrieved from the "Deploy Dynatrace" installer page. -| `oneagent_local_installer` | `-` | The Path to OneAgent installer stored on the main node. -| `oneagent_installer_arch` | `-` | Specifies the OneAgent installer architecture. -| `oneagent_version` | `latest` | The required version of the OneAgent in the `1.199.247.20200714-111723` format. See [Deployment API - GET available versions of OneAgent] for more details. -| `oneagent_download_dir` | Linux: `$TEMP` or `/tmp`
Windows: `%TEMP%` or `C:\Windows\Temp` | Installer download directory. For Linux and AIX, the directory must not contain spaces. Will be created if it does not exist. -| `oneagent_install_args` | `-` Dynatrace OneAgent installation parameters defined as a list of items. -| `oneagent_platform_install_args` | `-` | Additional list of platform-specific installation parameters, appended to `oneagent_install_args' when run on a respective platform. -| `oneagent_preserve_installer` | `false` | Preserve installers on secondary machines after deployment. -| `oneagent_package_state` | `present` | OneAgent package state; use `present` or `latest` to make sure it's installed, or `absent` in order to uninstall. -| `oneagent_reboot_host` | `false` | Reboot the secondary machine after OneAgent installation -| `oneagent_verify_signature` | `true` | Verifies installer's signature (available only on AIX/Linux platforms) -| `oneagent_reboot_timeout` | `3600` | Set the timeout for rebooting secondary machine in seconds +| Name | Default | Description | +| -------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `oneagent_environment_url` | `-` | The URL of the target Dynatrace environment (see [Direct download from your environment](#direct-download-from-your-environment)). | +| `oneagent_paas_token` | `-` | The [PaaS Token] retrieved from the "Deploy Dynatrace" installer page. | +| `oneagent_local_installer` | `-` | The Path to OneAgent installer stored on the main node. | +| `oneagent_installer_arch` | `-` | Specifies the OneAgent installer architecture. | +| `oneagent_version` | `latest` | The required version of the OneAgent in the `1.199.247.20200714-111723` format. See [Deployment API - GET available versions of OneAgent] for more details. | +| `oneagent_download_dir` | Linux: `$TEMP` or `/tmp`
Windows: `%TEMP%` or `C:\Windows\Temp` | Installer download directory. For Linux and AIX, the directory must not contain spaces. Will be created if it does not exist. | +| `oneagent_install_args` | `-` Dynatrace OneAgent installation parameters defined as a list of items. | +| `oneagent_platform_install_args` | `-` | Additional list of platform-specific installation parameters, appended to `oneagent_install_args' when run on a respective platform. | +| `oneagent_preserve_installer` | `false` | Preserve installers on secondary machines after deployment. | +| `oneagent_package_state` | `present` | OneAgent package state; use `present` or `latest` to make sure it's installed, or `absent` in order to uninstall. | +| `oneagent_reboot_host` | `false` | Reboot the secondary machine after OneAgent installation | +| `oneagent_verify_signature` | `true` | Verifies installer's signature (available only on AIX/Linux platforms) | +| `oneagent_reboot_timeout` | `3600` | Set the timeout for rebooting secondary machine in seconds | For more information, see customize OneAgent installation documentation for [Linux], [Windows], and [AIX]. ## Examples -You can find example playbooks in the `examples` directory within the role. The directory contains the following: - -`local_installer` - basic configuration with local installers. - -`advanced_config` - showing advanced configuration with a custom install path and download directory. - -`oneagentctl_config` - showing bare configuration with oneagentctl. +You can find example playbooks in the `examples` directory within the role. The directory contains the following: -`local_installer` - basic configuration with local installers. -`advanced_config` - showing advanced configuration with a custom install path and download directory. -`oneagentctl_config` - showing bare configuration with oneagentctl. Additionally, each directory contains inventory file with basic hosts configuration for playbooks. -__NOTE:__ For multi-platform Windows, Linux or AIX deployment, you must specify the `become: true` option for proper machines group in the inventory file. +**NOTE:** For multi-platform Windows, Linux or AIX deployment, you must specify the `become: true` option for proper machines group in the inventory file. On Windows, `become: true` option is not supported. Since Windows paths are different compared to a traditional Linux system, review [Path Formatting for Windows] to avoid issues during install. diff --git a/roles/oneagent/examples/advanced_config/advanced_config.yml b/roles/oneagent/examples/advanced_config/advanced_config.yml index 230d37d..e01f5da 100644 --- a/roles/oneagent/examples/advanced_config/advanced_config.yml +++ b/roles/oneagent/examples/advanced_config/advanced_config.yml @@ -3,8 +3,6 @@ Download OneAgent installer in specific version to a custom directory with additional OneAgent install parameters. Both linux_other and linux_arm have different user specified by platform args parameter. hosts: linux_other,linux_arm - collections: - - dynatrace.oneagent # credentials.yml file contains oneagent_environment_url and # oneagent_paas_token variables that needs to be stored securely vars_files: @@ -22,4 +20,4 @@ tasks: - name: Import Dynatrace OneAgent role ansible.builtin.import_role: - name: oneagent + name: dynatrace.oneagent.oneagent diff --git a/roles/oneagent/examples/local_installer/local_installer.yml b/roles/oneagent/examples/local_installer/local_installer.yml index fdc1237..7bf2bdc 100644 --- a/roles/oneagent/examples/local_installer/local_installer.yml +++ b/roles/oneagent/examples/local_installer/local_installer.yml @@ -3,9 +3,7 @@ Basic OneAgent installation using a local installer. Hosts placed in unix hosts groups have its local installer paths defined in inventory file. Main node communicates with Windows hosts over SSH. hosts: windows,unix - collections: - - dynatrace.oneagent tasks: - name: Import Dynatrace OneAgent role ansible.builtin.import_role: - name: oneagent + name: dynatrace.oneagent.oneagent diff --git a/roles/oneagent/examples/oneagentctl_config/oneagentctl_config.yml b/roles/oneagent/examples/oneagentctl_config/oneagentctl_config.yml index 343aed1..9308383 100644 --- a/roles/oneagent/examples/oneagentctl_config/oneagentctl_config.yml +++ b/roles/oneagent/examples/oneagentctl_config/oneagentctl_config.yml @@ -1,8 +1,6 @@ --- - name: Apply host level configuration with oneagentctl hosts: linux_other - collections: - - dynatrace.oneagent vars: oneagent_install_args: - --set-host-name=new_host_name @@ -12,4 +10,4 @@ tasks: - name: Import Dynatrace OneAgent role ansible.builtin.import_role: - name: oneagent + name: dynatrace.oneagent.oneagent diff --git a/roles/oneagent/tests/README.md b/roles/oneagent/tests/README.md index 7e49db4..3af44ff 100644 --- a/roles/oneagent/tests/README.md +++ b/roles/oneagent/tests/README.md @@ -1,30 +1,37 @@ # Component tests + The tests support two types of deployment: -- local - the tests are run on the same Unix machine as main node; + +- local - the tests are run on the same Unix machine as main node; - remote - the tests are run on a remote Windows (Unix is not supported at the moment) machine; -Currently, there is no option to mix these two types of deployment and the tests must be run for one platform at a time. + Currently, there is no option to mix these two types of deployment and the tests must be run for one platform at a time. ## Remote deployment + For remote deployment, regular OneAgent installers are used, which are downloaded from the Dynatrace environment during the tests. To use this type of deployment, the following parameters must be provided: + - `--user` - username for the remote machine; - `--password` - password for the remote machine; - `--tenant` - The environment URL from which the installer will be downloaded, in form of `https://abc123456.com`; - `--tenant_token` - Token for downloading the installer, generated in Deployment UI; - `--windows_x86=` - IP address of the remote Windows machine; -Failing to provide any of these parameters will result in failure. + Failing to provide any of these parameters will result in failure. ## Local deployment + For local deployment, the tests are using mocked version of the OneAgent installer, which simulates its basic behavior - returning version, deploying `uninstall.sh` script and creating `oneagentctl`, used for configuring installation. To use this type of deployment, the only required parameter is `--linux_x86=localhost`. In case, multiple platforms for local deployment are specified or any other platforms is used along with local one, only the first local platform is used. ## Requirements + - Python 3.10+ - pip 21.0+ - venv 20.0+ -- +- + ## Running tests ### Preparing test environment @@ -36,8 +43,7 @@ Upon downloading the collection $ apt-get install -y python3-venv python3-pip sshpass # Create virtual environment -$ python -m venv venv -$ source venv/bin/activate +$ python -m venv venv && source venv/bin/activate # Install requirements $ pip install -r roles/oneagent/tests/requirements.txt @@ -61,15 +67,15 @@ $ sudo bash -c "source venv/bin/activate && pytest roles/oneagent/tests --user=< --tenant=https://abc123456.com --tenant_token= --windows_x86=" ``` -There is also an option to run tests with placed-in installers using `--preserve-installers` switch. -In this mode, the test environment won't be fully cleaned. It requires that both installers differs in version and have +There is also an option to run tests with placed-in installers using `--preserve-installers` switch. +In this mode, the test environment won't be fully cleaned. It requires that both installers differs in version and have the naming schema from tenant, e.g. `Dynatrace-OneAgent-Linux-1.301.0.sh`, `Dynatrace-OneAgent-Linux-arm-1.302.0.sh`. Also, the installers certificate must be downloaded for successful run.
You can refer to [this documentation](https://docs.dynatrace.com/docs/shortlink/api-deployment-get-versions) on how to list available installers. For downloading the specific version of the OneAgent, visit [this documentation](https://docs.dynatrace.com/docs/shortlink/api-deployment-get-oneagent-version).
-To run tests in this mode, download 2 versions of installers you want along with the certificate and place them in +To run tests in this mode, download 2 versions of installers you want along with the certificate and place them in `test_dir/installers` directory. Then, you can run the tests. ```console diff --git a/roles/oneagent/tests/ansible/config.py b/roles/oneagent/tests/ansible/config.py index 52386ea..e63b4b8 100644 --- a/roles/oneagent/tests/ansible/config.py +++ b/roles/oneagent/tests/ansible/config.py @@ -6,21 +6,16 @@ ANSIBLE_PASS_KEY, ANSIBLE_RESOURCE_DIR, ANSIBLE_USER_KEY, - COLLECTION_NAME, - COLLECTION_NAMESPACE, CREDENTIALS_FILE_NAME, HOSTS_TEMPLATE_FILE_NAME, INSTALLED_COLLECTIONS_DIR, INVENTORY_FILE, PLAYBOOK_FILE, PLAYBOOK_TEMPLATE_FILE_NAME, - ROLE_NAME, TEST_COLLECTIONS_DIR, - TEST_SIGNATURE_FILE, ) - from util.common_utils import read_yaml_file, write_yaml_file -from util.constants.common_constants import TEST_DIRECTORY, INSTALLERS_DIRECTORY, INSTALLER_CERTIFICATE_FILE_NAME +from util.constants.common_constants import TEST_DIRECTORY from util.test_data_types import DeploymentPlatform, PlatformCollection @@ -31,7 +26,8 @@ def _prepare_collection() -> None: def _prepare_playbook_file() -> None: shutil.copy( - str(ANSIBLE_RESOURCE_DIR / PLAYBOOK_TEMPLATE_FILE_NAME), str(TEST_DIRECTORY / PLAYBOOK_TEMPLATE_FILE_NAME) + str(ANSIBLE_RESOURCE_DIR / PLAYBOOK_TEMPLATE_FILE_NAME), + str(TEST_DIRECTORY / PLAYBOOK_TEMPLATE_FILE_NAME), ) @@ -65,26 +61,26 @@ class AnsibleConfig: PARAM_SECTION_KEY = "vars" # Platform-agnostic - PAAS_TOKEN_KEY = "oneagent_paas_token" - PACKAGE_STATE_KEY = "oneagent_package_state" - INSTALLER_ARGS_KEY = "oneagent_install_args" ENVIRONMENT_URL_KEY = "oneagent_environment_url" - VERIFY_SIGNATURE_KEY = "oneagent_verify_signature" + INSTALLER_ARGS_KEY = "oneagent_install_args" INSTALLER_VERSION_KEY = "oneagent_version" + PAAS_TOKEN_KEY = "oneagent_paas_token" + PACKAGE_STATE_KEY = "oneagent_package_state" PRESERVE_INSTALLER_KEY = "oneagent_preserve_installer" + VERIFY_SIGNATURE_KEY = "oneagent_verify_signature" # Internal parameters - FORCE_CERT_DOWNLOAD_KEY = "oneagent_force_cert_download" CA_CERT_DOWNLOAD_CERT_KEY = "oneagent_ca_cert_download_cert" - VALIDATE_DOWNLOAD_CERTS_KEY = "oneagent_validate_certs" - INSTALLER_DOWNLOAD_CERT_KEY = "oneagent_installer_download_cert" CA_CERT_DOWNLOAD_URL_KEY = "oneagent_ca_cert_download_url" + FORCE_CERT_DOWNLOAD_KEY = "oneagent_force_cert_download" + INSTALLER_DOWNLOAD_CERT_KEY = "oneagent_installer_download_cert" + VALIDATE_DOWNLOAD_CERTS_KEY = "oneagent_validate_certs" # Platform-specific DOWNLOAD_DIR_KEY = "oneagent_download_dir" - LOCAL_INSTALLER_KEY = "oneagent_local_installer" INSTALLER_ARCH_KEY = "oneagent_installer_arch" INSTALLER_PLATFORM_ARGS_KEY = "oneagent_platform_install_args" + LOCAL_INSTALLER_KEY = "oneagent_local_installer" def __init__(self, user: str, password: str, platforms: PlatformCollection): self.user = user diff --git a/roles/oneagent/tests/ansible/constants.py b/roles/oneagent/tests/ansible/constants.py index bdfdbb7..54d87f4 100644 --- a/roles/oneagent/tests/ansible/constants.py +++ b/roles/oneagent/tests/ansible/constants.py @@ -1,10 +1,10 @@ from pathlib import Path from util.constants.common_constants import ( - RESOURCES_DIRECTORY, INSTALLER_CERTIFICATE_FILE_NAME, - TEST_DIRECTORY, INSTALLERS_DIRECTORY, + RESOURCES_DIRECTORY, + TEST_DIRECTORY, ) # Internal @@ -34,8 +34,16 @@ PLAYBOOK_FILE = TEST_DIRECTORY / PLAYBOOK_TEMPLATE_FILE_NAME INVENTORY_FILE = TEST_DIRECTORY / HOSTS_TEMPLATE_FILE_NAME TEST_COLLECTIONS_DIR = TEST_DIRECTORY / "collections" -TEST_SIGNATURE_FILE = TEST_COLLECTIONS_DIR / "ansible_collections" / COLLECTION_NAMESPACE /\ - COLLECTION_NAME / "roles" / ROLE_NAME / "files" / INSTALLER_CERTIFICATE_FILE_NAME +TEST_SIGNATURE_FILE = ( + TEST_COLLECTIONS_DIR + / "ansible_collections" + / COLLECTION_NAMESPACE + / COLLECTION_NAME + / "roles" + / ROLE_NAME + / "files" + / INSTALLER_CERTIFICATE_FILE_NAME +) # Public LOCAL_INSTALLERS_LOCATION = INSTALLERS_DIRECTORY diff --git a/roles/oneagent/tests/ansible/runner.py b/roles/oneagent/tests/ansible/runner.py index f8e8217..69e691e 100644 --- a/roles/oneagent/tests/ansible/runner.py +++ b/roles/oneagent/tests/ansible/runner.py @@ -1,7 +1,12 @@ -import subprocess import logging +import subprocess -from ansible.constants import HOSTS_TEMPLATE_FILE_NAME, PLAYBOOK_TEMPLATE_FILE_NAME, CREDENTIALS_FILE_NAME, TEST_DIRECTORY +from ansible.constants import ( + CREDENTIALS_FILE_NAME, + HOSTS_TEMPLATE_FILE_NAME, + PLAYBOOK_TEMPLATE_FILE_NAME, + TEST_DIRECTORY, +) from util.test_data_types import CommandResult, DeploymentResult @@ -11,17 +16,22 @@ def __init__(self, user: str, password: str): self.password = password def run_deployment(self) -> DeploymentResult: - with open(TEST_DIRECTORY / PLAYBOOK_TEMPLATE_FILE_NAME, 'r') as f: - logging.debug(f"Running playbook ({PLAYBOOK_TEMPLATE_FILE_NAME}):\n{f.read()}") + with open(TEST_DIRECTORY / PLAYBOOK_TEMPLATE_FILE_NAME, "r") as f: + logging.debug("Running playbook (%s):\n%s", PLAYBOOK_TEMPLATE_FILE_NAME, f.read()) - with open(TEST_DIRECTORY / HOSTS_TEMPLATE_FILE_NAME, 'r') as f: - logging.debug(f"Inventory file ({HOSTS_TEMPLATE_FILE_NAME}):\n{f.read()}") + with open(TEST_DIRECTORY / HOSTS_TEMPLATE_FILE_NAME, "r") as f: + logging.debug("Inventory file (%s):\n%s", HOSTS_TEMPLATE_FILE_NAME, f.read()) - with open(TEST_DIRECTORY / CREDENTIALS_FILE_NAME, 'r') as f: - logging.debug(f"Credentials file ({CREDENTIALS_FILE_NAME}):\n{f.read()}") + with open(TEST_DIRECTORY / CREDENTIALS_FILE_NAME, "r") as f: + logging.debug("Credentials file (%s):\n%s", CREDENTIALS_FILE_NAME, f.read()) res = subprocess.run( - ["ansible-playbook", "-i", TEST_DIRECTORY / HOSTS_TEMPLATE_FILE_NAME, TEST_DIRECTORY / PLAYBOOK_TEMPLATE_FILE_NAME], + [ + "ansible-playbook", + "-i", + TEST_DIRECTORY / HOSTS_TEMPLATE_FILE_NAME, + TEST_DIRECTORY / PLAYBOOK_TEMPLATE_FILE_NAME, + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, diff --git a/roles/oneagent/tests/command/platform_command_wrapper.py b/roles/oneagent/tests/command/platform_command_wrapper.py index 20be60d..9121371 100644 --- a/roles/oneagent/tests/command/platform_command_wrapper.py +++ b/roles/oneagent/tests/command/platform_command_wrapper.py @@ -3,7 +3,7 @@ from command.command_wrapper import CommandWrapper from command.unix.unix_command_wrapper import UnixCommandWrapper from command.windows.windows_command_wrapper import WindowsCommandWrapper -from util.test_data_types import DeploymentPlatform, CommandResult +from util.test_data_types import CommandResult, DeploymentPlatform class PlatformCommandWrapper: diff --git a/roles/oneagent/tests/command/unix/unix_command_wrapper.py b/roles/oneagent/tests/command/unix/unix_command_wrapper.py index 4050c3b..9360c8e 100644 --- a/roles/oneagent/tests/command/unix/unix_command_wrapper.py +++ b/roles/oneagent/tests/command/unix/unix_command_wrapper.py @@ -1,10 +1,9 @@ +import subprocess from pathlib import Path from command.command_wrapper import CommandWrapper from util.test_data_types import CommandResult -import subprocess - class UnixCommandWrapper(CommandWrapper): def __init__(self, user: str, password: str): @@ -12,7 +11,12 @@ def __init__(self, user: str, password: str): def _execute(self, address: str, command: str, *args: str) -> CommandResult: out = subprocess.run( - " ".join([command, *args]), stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, check=False, shell=True + " ".join([command, *args]), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + check=False, + shell=True, ) return CommandResult(out.returncode, out.stdout, out.stderr) diff --git a/roles/oneagent/tests/command/windows/windows_command_executor.py b/roles/oneagent/tests/command/windows/windows_command_executor.py index 4c62c01..20f2c58 100644 --- a/roles/oneagent/tests/command/windows/windows_command_executor.py +++ b/roles/oneagent/tests/command/windows/windows_command_executor.py @@ -1,5 +1,4 @@ import winrm - from util.test_data_types import CommandResult diff --git a/roles/oneagent/tests/command/windows/windows_command_wrapper.py b/roles/oneagent/tests/command/windows/windows_command_wrapper.py index ad153b5..f4253ad 100644 --- a/roles/oneagent/tests/command/windows/windows_command_wrapper.py +++ b/roles/oneagent/tests/command/windows/windows_command_wrapper.py @@ -13,7 +13,7 @@ def get_file_content(self, address: str, file: Path) -> CommandResult: return self.executor.execute(address, "type", str(file)) def file_exists(self, address: str, file: Path) -> CommandResult: - # Windows needs double quoting for passing paths containing + # Windows needs double quoting for passing paths # containing spaces, single quotes don't work return self.executor.execute(address, f'if exist "{file}" (exit 0) else (exit 1)') @@ -24,6 +24,7 @@ def _run_directory_creation_command(self, address: str, directory: Path) -> Comm result = CommandResult(0, "", "") if self.directory_exists(address, directory).returncode == 1: result = self.executor.execute(address, "md", str(directory)) + return result def create_directory(self, address: str, directory: Path) -> CommandResult: diff --git a/roles/oneagent/tests/conftest.py b/roles/oneagent/tests/conftest.py index a60b3dc..c88eff4 100644 --- a/roles/oneagent/tests/conftest.py +++ b/roles/oneagent/tests/conftest.py @@ -1,24 +1,28 @@ # PYTEST CONFIGURATION FILE import logging -import pytest +import os import shutil +import socket import subprocess import sys -import os -import socket from pathlib import Path from typing import Any -from command.platform_command_wrapper import PlatformCommandWrapper +import pytest from ansible.config import AnsibleConfig from ansible.runner import AnsibleRunner -from util.installer_provider import generate_installers, download_signature, download_installers +from command.platform_command_wrapper import PlatformCommandWrapper from util.common_utils import prepare_test_dirs -from util.test_data_types import DeploymentPlatform, PlatformCollection, DeploymentResult +from util.constants.common_constants import ( + COMPONENT_TEST_BASE, + INSTALLERS_DIRECTORY, + LOG_DIRECTORY, + SERVER_DIRECTORY, + TEST_DIRECTORY, +) +from util.installer_provider import download_installers, download_signature, generate_installers +from util.test_data_types import DeploymentPlatform, DeploymentResult, PlatformCollection from util.test_helpers import check_agent_state, perform_operation_on_platforms -from util.constants.common_constants import (TEST_DIRECTORY, INSTALLERS_DIRECTORY, COMPONENT_TEST_BASE, - SERVER_DIRECTORY, LOG_DIRECTORY) - # Command line options USER_KEY = "user" @@ -38,7 +42,7 @@ def is_local_deployment(platforms: PlatformCollection) -> bool: - return any("localhost" in hosts for _, hosts in platforms.items()) + return any("localhost" in hosts for platform, hosts in platforms.items()) def parse_platforms_from_options(options: dict[str, Any]) -> PlatformCollection: @@ -48,7 +52,7 @@ def parse_platforms_from_options(options: dict[str, Any]) -> PlatformCollection: for key, hosts in options.items(): if key in deployment_platforms and hosts: if "localhost" in hosts: - logging.info(f"Local deployment detected for {key}, only this host will be used") + logging.info("Local deployment detected for %s, only this host will be used", key) return {DeploymentPlatform.from_str(key): hosts} platforms[DeploymentPlatform.from_str(key)] = hosts return platforms @@ -98,16 +102,26 @@ def prepare_installers(request) -> None: @pytest.fixture(scope="session", autouse=True) def installer_server_url(request) -> None: port = 8021 - ip_address = socket.gethostbyname(socket.gethostname()) - url = f"https://{ip_address}:{port}" - - logging.info(f"Running server on {url}...") - - proc = subprocess.Popen([sys.executable, "-m", "server", "--port", f"{port}", "--ip-address", ip_address], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - encoding="utf-8", - env={"PYTHONPATH": f"{Path(__file__).resolve().parent}"}) + ipaddress = socket.gethostbyname(socket.gethostname()) + url = f"https://{ipaddress}:{port}" + + logging.info("Running server on %s...", url) + + proc = subprocess.Popen( + [ + sys.executable, + "-m", + "server", + "--port", + f"{port}", + "--ip-address", + ipaddress, + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="utf-8", + env={"PYTHONPATH": f"{Path(__file__).resolve().parent}"}, + ) yield url @@ -147,13 +161,33 @@ def pytest_addoption(parser) -> None: parser.addoption(f"--{USER_KEY}", type=str, help="Name of the user", required=False) parser.addoption(f"--{PASS_KEY}", type=str, help="Password of the user", required=False) - parser.addoption(f"--{TENANT_KEY}", type=str, help="Tenant URL for downloading installer", required=False) - parser.addoption(f"--{TENANT_TOKEN_KEY}", type=str, help="API key for downloading installer", required=False) - parser.addoption(f"--{PRESERVE_INSTALLERS_KEY}", type=bool, default=False, help="Preserve installers after test run", required=False) + parser.addoption( + f"--{TENANT_KEY}", + type=str, + help="Tenant URL for downloading installer", + required=False, + ) + parser.addoption( + f"--{TENANT_TOKEN_KEY}", + type=str, + help="API key for downloading installer", + required=False, + ) + parser.addoption( + f"--{PRESERVE_INSTALLERS_KEY}", + type=bool, + default=False, + help="Preserve installers after test run", + required=False, + ) for platform in DeploymentPlatform: parser.addoption( - f"--{platform.value}", type=str, nargs="+", default=[], help="List of IPs for specified platform" + f"--{platform.value}", + type=str, + nargs="+", + default=[], + help="List of IPs for specified platform", ) @@ -189,4 +223,4 @@ def pytest_generate_tests(metafunc) -> None: metafunc.parametrize(PLATFORMS_KEY, [platforms]) if WRAPPER_KEY in metafunc.fixturenames: - metafunc.parametrize(WRAPPER_KEY, [wrapper]) \ No newline at end of file + metafunc.parametrize(WRAPPER_KEY, [wrapper]) diff --git a/roles/oneagent/tests/resources/ansible/oneagent.yml b/roles/oneagent/tests/resources/ansible/oneagent.yml index b966231..f8dfda5 100644 --- a/roles/oneagent/tests/resources/ansible/oneagent.yml +++ b/roles/oneagent/tests/resources/ansible/oneagent.yml @@ -5,6 +5,6 @@ - credentials.yml vars: {} tasks: - - name: Import OneAgent role + - name: Import Dynatrace OneAgent role ansible.builtin.import_role: name: dynatrace.oneagent.oneagent diff --git a/roles/oneagent/tests/resources/installers/Dynatrace-OneAgent-Linux.sh b/roles/oneagent/tests/resources/installers/Dynatrace-OneAgent-Linux.sh index 796e4a6..8af1ec4 100644 --- a/roles/oneagent/tests/resources/installers/Dynatrace-OneAgent-Linux.sh +++ b/roles/oneagent/tests/resources/installers/Dynatrace-OneAgent-Linux.sh @@ -1,24 +1,26 @@ -#!/bin/sh +#!/usr/bin/env bash # This script acts as a self contained installer of the procuct -set -e +set -eu readonly DEFAULT_INSTALL_DIR="/opt/dynatrace/oneagent" readonly INSTALLER_VERSION="##VERSION##" readonly DEPLOYMENT_CONF_PATH="/var/lib/dynatrace/oneagent/agent/config" readonly UNINSTALL_SCRIPT="uninstall.sh" -readonly UNINSTALL_CODE="$(cat <<-ENDUNINSTALL +UNINSTALL_CODE="$(cat <<-ENDUNINSTALL ##UNINSTALL_CODE## ENDUNINSTALL )" +readonly UNINSTALL_CODE readonly ONEAGENTCTL_BIN="oneagentctl" -readonly ONEAGENTCTL_CODE="$(cat <<-ENDCTL +ONEAGENTCTL_CODE="$(cat <<-ENDCTL ##ONEAGENTCTL_CODE## ENDCTL )" +readonly ONEAGENTCTL_CODE CTL_PARAMS= INSTALL_DIR="${DEFAULT_INSTALL_DIR}" @@ -49,17 +51,18 @@ deployOneagentCtl() { local ONEAGENTCTL_DIR="${INSTALL_DIR}/agent/tools" mkdir -p "${ONEAGENTCTL_DIR}" mkdir -p "${DEPLOYMENT_CONF_PATH}" - printf '%s' "${ONEAGENTCTL_CODE}" > "${ONEAGENTCTL_DIR}/${ONEAGENTCTL_BIN}" + printf '%s' "${ONEAGENTCTL_CODE}" >"${ONEAGENTCTL_DIR}/${ONEAGENTCTL_BIN}" chmod +x "${ONEAGENTCTL_DIR}/${ONEAGENTCTL_BIN}" } deployUninstallScript() { local UNINSTALL_DIR="${INSTALL_DIR}/agent" mkdir -p "${UNINSTALL_DIR}" - printf '%s' "${UNINSTALL_CODE}" > "${UNINSTALL_DIR}/${UNINSTALL_SCRIPT}" + printf '%s' "${UNINSTALL_CODE}" >"${UNINSTALL_DIR}/${UNINSTALL_SCRIPT}" chmod +x "${UNINSTALL_DIR}/${UNINSTALL_SCRIPT}" } +# shellcheck disable=SC2086 applyConfig() { "${INSTALL_DIR}/agent/tools/${ONEAGENTCTL_BIN}" ${CTL_PARAMS} } diff --git a/roles/oneagent/tests/resources/installers/oneagentctl.sh b/roles/oneagent/tests/resources/installers/oneagentctl.sh index 08da32a..13345a5 100644 --- a/roles/oneagent/tests/resources/installers/oneagentctl.sh +++ b/roles/oneagent/tests/resources/installers/oneagentctl.sh @@ -1,8 +1,8 @@ -#!/bin/bash +#!/usr/bin/env bash -# This file simulates deployment functionalities of oneagentctl binary, used to configure installation. +# This script simulates deployment functionalities of oneagentctl binary, used to configure installation. -set -e +set -eu readonly INSTALLER_VERSION="##VERSION##" readonly DEPLOYMENT_CONF_PATH="/var/lib/dynatrace/oneagent/agent/config" @@ -18,29 +18,40 @@ saveToConfig() { while [ $# -gt 0 ]; do # example command: --set-host-property=TENANT=tenant1 # setter: --set-host-property - local setter="$(cutVariable "${1}" "=" 1)" + local setter + setter="$(cutVariable "${1}" "=" 1)" + # setterType: property - local setterType="$(cutVariable "${setter}" "-" "5-")" + local setterType + setterType="$(cutVariable "${setter}" "-" "5-")" + # value: TENANT=tenant1 - local value="$(cutVariable "${1}" "=" "2-")" + local value + value="$(cutVariable "${1}" "=" "2-")" + # property: TENANT - local property="$(cutVariable "${value}" "=" "1")" + local property + property="$(cutVariable "${value}" "=" "1")" + local setterFile="${DEPLOYMENT_CONF_PATH}/${setterType}" if grep -q "${property}" "${setterFile}"; then sed -i "s/${property}.*/${value}/" "${setterFile}" else printf '%s\n' "${value}" >>"${setterFile}" fi + shift done } readFromConfig() { - local getterType="$(cutVariable "${1}" "-" "5-")" + local getterType + getterType="$(cutVariable "${1}" "-" "5-")" if [ "${getterType}" = "properties" ]; then getterType="property" fi + getterType="$(cutVariable "${getterType}" "s" "1")" cat "${DEPLOYMENT_CONF_PATH}/${getterType}" } @@ -49,7 +60,7 @@ main() { if [ "${1}" = '--version' ]; then printf '%s\n' "${INSTALLER_VERSION}" elif printf "%s" "${1}" | grep -q "^--get"; then - readFromConfig ${1} + readFromConfig "${1}" elif printf "%s" "${1}" | grep -q "^--set"; then saveToConfig "$@" fi diff --git a/roles/oneagent/tests/resources/installers/uninstall.sh b/roles/oneagent/tests/resources/installers/uninstall.sh index 9bb2649..1219ac3 100644 --- a/roles/oneagent/tests/resources/installers/uninstall.sh +++ b/roles/oneagent/tests/resources/installers/uninstall.sh @@ -1,8 +1,8 @@ -#!/bin/sh +#!/usr/bin/env bash -# This file simulates the basic behavior of the uninstall.sh script +# This script simulates the basic behavior of the uninstall.sh script -set -e +set -eu main() { rm -rf /opt/dynatrace diff --git a/roles/oneagent/tests/server/server.py b/roles/oneagent/tests/server/server.py index efe0f12..3991a35 100644 --- a/roles/oneagent/tests/server/server.py +++ b/roles/oneagent/tests/server/server.py @@ -4,11 +4,14 @@ from typing import Any from flask import Blueprint, Flask, request, send_file - from util.common_utils import get_installers -from util.constants.common_constants import (INSTALLERS_DIRECTORY, SERVER_DIRECTORY, INSTALLER_CERTIFICATE_FILE_NAME, - SERVER_CERTIFICATE_FILE_NAME, SERVER_PRIVATE_KEY_FILE_NAME) - +from util.constants.common_constants import ( + INSTALLER_CERTIFICATE_FILE_NAME, + INSTALLERS_DIRECTORY, + SERVER_CERTIFICATE_FILE_NAME, + SERVER_DIRECTORY, + SERVER_PRIVATE_KEY_FILE_NAME, +) from util.ssl_certificate_generator import SSLCertificateGenerator app = Flask(__name__) @@ -19,7 +22,7 @@ def get_installer(system: str, arch: str, version: str) -> TransferResult: - logging.info(f"Getting installer for system {system}_{arch} in {version} version") + logging.info("Getting installer for system %s_%s in %s version", system, arch, version) installers = get_installers(system, arch, version, True) @@ -36,7 +39,7 @@ def get_installer(system: str, arch: str, version: str) -> TransferResult: def get_ca_certificate() -> TransferResult: cert_file = INSTALLERS_DIRECTORY / INSTALLER_CERTIFICATE_FILE_NAME if not cert_file.exists(): - logging.warning(f"{cert_file} not found") + logging.warning("%s not found", cert_file) return f"{cert_file} not found", HTTPStatus.NOT_FOUND return send_file(cert_file) @@ -53,11 +56,18 @@ def get_agent_in_version(system, version) -> TransferResult: def main() -> None: logging.basicConfig( - format="%(asctime)s [server] %(levelname)s: %(message)s", datefmt="%H:%M:%S", level=logging.INFO + format="%(asctime)s [server] %(levelname)s: %(message)s", + datefmt="%H:%M:%S", + level=logging.INFO, ) - parser = argparse.ArgumentParser(description='Run Flask server.') - parser.add_argument("--ip-address", type=str, dest='ip_address', help="IP address of the host to run the server on") + parser = argparse.ArgumentParser(description="Run Flask server.") + parser.add_argument( + "--ip-address", + type=str, + dest="ip_address", + help="IP address of the host to run the server on", + ) parser.add_argument("--port", type=int, help="Port to run the server on") args = parser.parse_args() @@ -66,12 +76,17 @@ def main() -> None: state_name="California", locality_name="San Francisco", organization_name="Dynatrace", - common_name=args.ip_address + common_name=args.ip_address, + ) + generator.generate_and_save( + f"{SERVER_DIRECTORY /SERVER_PRIVATE_KEY_FILE_NAME}", + f"{SERVER_DIRECTORY / SERVER_CERTIFICATE_FILE_NAME}", ) - generator.generate_and_save(f"{SERVER_DIRECTORY / SERVER_PRIVATE_KEY_FILE_NAME}", - f"{SERVER_DIRECTORY / SERVER_CERTIFICATE_FILE_NAME}") - context = (f"{SERVER_DIRECTORY / SERVER_CERTIFICATE_FILE_NAME}", f"{SERVER_DIRECTORY / SERVER_PRIVATE_KEY_FILE_NAME}") + context = ( + f"{SERVER_DIRECTORY / SERVER_CERTIFICATE_FILE_NAME}", + f"{SERVER_DIRECTORY / SERVER_PRIVATE_KEY_FILE_NAME}", + ) app.register_blueprint(installer_bp, url_prefix="/api/v1/deployment/installer/agent") app.register_blueprint(certificate_bp) app.run(host="0.0.0.0", debug=True, ssl_context=context, port=args.port) diff --git a/roles/oneagent/tests/test_installAndConfig.py b/roles/oneagent/tests/test_installAndConfig.py index 662e962..8a872f6 100644 --- a/roles/oneagent/tests/test_installAndConfig.py +++ b/roles/oneagent/tests/test_installAndConfig.py @@ -9,9 +9,9 @@ from util.test_helpers import ( check_agent_state, check_download_directory, - set_installer_download_params, perform_operation_on_platforms, run_deployment, + set_installer_download_params, ) UNIX_DOWNLOAD_PATH = Path("/tmp/dyna") @@ -42,7 +42,11 @@ def _assert_oneagentctl_getter( def _check_install_args( - platform: DeploymentPlatform, address: str, wrapper: PlatformCommandWrapper, ansible: str) -> None: + platform: DeploymentPlatform, + address: str, + wrapper: PlatformCommandWrapper, + ansible: str, +) -> None: logging.debug("Platform: %s, IP: %s", platform, address) oneagentctl = f"{get_oneagentctl_path(platform)}" @@ -56,8 +60,13 @@ def _check_install_args( assert params[TECH_NAME_KEY] is not None and params[TECH_NAME_KEY] == ansible -def _check_config_args(platform: DeploymentPlatform, address: str, wrapper: PlatformCommandWrapper, expected_tags: set[str], - expected_properties: set[str]): +def _check_config_args( + platform: DeploymentPlatform, + address: str, + wrapper: PlatformCommandWrapper, + expected_tags: set[str], + expected_properties: set[str], +): logging.debug("Platform: %s, IP: %s", platform, address) _assert_oneagentctl_getter(platform, address, wrapper, CTL_OPTION_GET_HOST_TAGS, expected_tags) @@ -81,16 +90,24 @@ def test_basic_installation(runner, configurator, platforms, wrapper, installer_ set_installer_download_params(configurator, installer_server_url) configurator.set_common_parameter(configurator.VALIDATE_DOWNLOAD_CERTS_KEY, False) configurator.set_common_parameter(configurator.PRESERVE_INSTALLER_KEY, True) - configurator.set_common_parameter(configurator.INSTALLER_ARGS_KEY, - [f"{CTL_OPTION_SET_HOST_TAG}={dummy_common_tag}", - f"{CTL_OPTION_SET_HOST_PROPERTY}={dummy_common_property}"]) + configurator.set_common_parameter( + configurator.INSTALLER_ARGS_KEY, + [ + f"{CTL_OPTION_SET_HOST_TAG}={dummy_common_tag}", + f"{CTL_OPTION_SET_HOST_PROPERTY}={dummy_common_property}", + ], + ) - for platform, _ in platforms.items(): + for platform, hosts in platforms.items(): download_dir: Path = get_platform_argument(platform, UNIX_DOWNLOAD_PATH, WINDOWS_DOWNLOAD_PATH) configurator.set_platform_parameter(platform, configurator.DOWNLOAD_DIR_KEY, str(download_dir)) - configurator.set_common_parameter(configurator.INSTALLER_PLATFORM_ARGS_KEY, - [f"{CTL_OPTION_SET_HOST_TAG}={dummy_platform_tag}", - f"{CTL_OPTION_SET_HOST_PROPERTY}={dummy_platform_property}"]) + configurator.set_common_parameter( + configurator.INSTALLER_PLATFORM_ARGS_KEY, + [ + f"{CTL_OPTION_SET_HOST_TAG}={dummy_platform_tag}", + f"{CTL_OPTION_SET_HOST_PROPERTY}={dummy_platform_property}", + ], + ) result = run_deployment(runner) @@ -102,15 +119,22 @@ def test_basic_installation(runner, configurator, platforms, wrapper, installer_ logging.info("Check if installer was downloaded to correct place and preserved") perform_operation_on_platforms( - platforms, check_download_directory, wrapper, True, UNIX_DOWNLOAD_PATH, WINDOWS_DOWNLOAD_PATH + platforms, + check_download_directory, + wrapper, + True, + UNIX_DOWNLOAD_PATH, + WINDOWS_DOWNLOAD_PATH, ) logging.info("Check if config args were applied correctly") - perform_operation_on_platforms(platforms, - _check_config_args, - wrapper, - {dummy_common_tag, dummy_platform_tag}, - {dummy_common_property, dummy_platform_property}) + perform_operation_on_platforms( + platforms, + _check_config_args, + wrapper, + {dummy_common_tag, dummy_platform_tag}, + {dummy_common_property, dummy_platform_property}, + ) logging.info("Check if installer args were passed correctly") perform_operation_on_platforms(platforms, _check_install_args, wrapper, TECH_NAME) diff --git a/roles/oneagent/tests/test_localInstaller.py b/roles/oneagent/tests/test_localInstaller.py index 82ab7a9..6d64fa2 100644 --- a/roles/oneagent/tests/test_localInstaller.py +++ b/roles/oneagent/tests/test_localInstaller.py @@ -9,7 +9,7 @@ check_download_directory, perform_operation_on_platforms, run_deployment, - set_ca_cert_download_params + set_ca_cert_download_params, ) @@ -18,11 +18,13 @@ def test_local_installer(runner, configurator, platforms, wrapper, installer_ser set_ca_cert_download_params(configurator, installer_server_url) - for platform, _ in platforms.items(): + for platform, hosts in platforms.items(): installers_location = LOCAL_INSTALLERS_LOCATION latest_installer_name = get_installers(platform.system(), platform.arch(), "latest")[-1] configurator.set_platform_parameter( - platform, configurator.LOCAL_INSTALLER_KEY, f"{installers_location}/{latest_installer_name}" + platform, + configurator.LOCAL_INSTALLER_KEY, + f"{installers_location}/{latest_installer_name}", ) run_deployment(runner) @@ -32,5 +34,10 @@ def test_local_installer(runner, configurator, platforms, wrapper, installer_ser logging.info("Check if installer was removed") perform_operation_on_platforms( - platforms, check_download_directory, wrapper, False, UNIX_DEFAULT_DOWNLOAD_PATH, WINDOWS_DEFAULT_DOWNLOAD_PATH + platforms, + check_download_directory, + wrapper, + False, + UNIX_DEFAULT_DOWNLOAD_PATH, + WINDOWS_DEFAULT_DOWNLOAD_PATH, ) diff --git a/roles/oneagent/tests/test_resilience.py b/roles/oneagent/tests/test_resilience.py index 2893ef6..0036990 100644 --- a/roles/oneagent/tests/test_resilience.py +++ b/roles/oneagent/tests/test_resilience.py @@ -2,23 +2,22 @@ import re import pytest - -from ansible.constants import TEST_SIGNATURE_FILE, ERROR_MESSAGES_FILE, FAILED_DEPLOYMENT_EXIT_CODE, VARIABLE_PREFIX +from ansible.constants import ERROR_MESSAGES_FILE, FAILED_DEPLOYMENT_EXIT_CODE, TEST_SIGNATURE_FILE, VARIABLE_PREFIX from util.common_utils import read_yaml_file from util.test_data_types import DeploymentResult -from util.test_helpers import run_deployment, set_installer_download_params, enable_for_system_family +from util.test_helpers import enable_for_system_family, run_deployment, set_installer_download_params -MISSING_REQUIRED_PARAMETERS_KEY = "missing_mandatory_params" -UNKNOWN_ARCHITECTURE_KEY = "unknown_arch" -LOCAL_INSTALLER_NOT_AVAILABLE_KEY = "missing_local_installer" -INSTALL_DIR_CONTAINS_SPACES_KEY = "install_dir_contains_spaces" DOWNLOAD_DIR_CONTAINS_SPACES_KEY = "download_dir_contains_spaces" +DOWNLOAD_FAILED_KEY = "failed_download" +INSTALL_DIR_CONTAINS_SPACES_KEY = "install_dir_contains_spaces" +LOCAL_INSTALLER_NOT_AVAILABLE_KEY = "missing_local_installer" MISSING_DOWNLOAD_DIRECTORY_KEY = "missing_download_dir" -VERSION_PARAMETER_TOO_LOW_KEY = "version_lower_than_minimal" +MISSING_REQUIRED_PARAMETERS_KEY = "missing_mandatory_params" MULTIPLE_INSTALL_PATH_KEY = "multiple_install_dir" -DOWNLOAD_FAILED_KEY = "failed_download" SIGNATURE_VERIFICATION_FAILED_KEY = "signature_verification_failed" +UNKNOWN_ARCHITECTURE_KEY = "unknown_arch" VERSION_LOWER_THAN_INSTALLED_KEY = "version_lower_than_installed" +VERSION_PARAMETER_TOO_LOW_KEY = "version_lower_than_minimal" def _parse_error_messages_file() -> dict[str, str]: @@ -71,7 +70,9 @@ def test_invalid_architecture(_error_messages, runner, configurator, installer_s configurator.set_common_parameter(configurator.INSTALLER_ARCH_KEY, "unknown_arch") _check_deployment_failure( - run_deployment(runner, True), _error_messages[UNKNOWN_ARCHITECTURE_KEY], FAILED_DEPLOYMENT_EXIT_CODE + run_deployment(runner, True), + _error_messages[UNKNOWN_ARCHITECTURE_KEY], + FAILED_DEPLOYMENT_EXIT_CODE, ) @@ -135,7 +136,9 @@ def test_multiple_install_path_arguments(_error_messages, runner, configurator, configurator.set_common_parameter(configurator.INSTALLER_PLATFORM_ARGS_KEY, ["INSTALL_PATH=/path2"]) _check_deployment_failure( - run_deployment(runner, True), _error_messages[MULTIPLE_INSTALL_PATH_KEY], FAILED_DEPLOYMENT_EXIT_CODE + run_deployment(runner, True), + _error_messages[MULTIPLE_INSTALL_PATH_KEY], + FAILED_DEPLOYMENT_EXIT_CODE, ) @@ -146,7 +149,9 @@ def test_failed_download(_error_messages, runner, configurator, installer_server configurator.set_common_parameter(configurator.ENVIRONMENT_URL_KEY, "0.0.0.0") _check_deployment_failure( - run_deployment(runner, True), _error_messages[DOWNLOAD_FAILED_KEY], FAILED_DEPLOYMENT_EXIT_CODE + run_deployment(runner, True), + _error_messages[DOWNLOAD_FAILED_KEY], + FAILED_DEPLOYMENT_EXIT_CODE, ) diff --git a/roles/oneagent/tests/test_upgrade.py b/roles/oneagent/tests/test_upgrade.py index 25eaac3..57f6901 100644 --- a/roles/oneagent/tests/test_upgrade.py +++ b/roles/oneagent/tests/test_upgrade.py @@ -1,26 +1,32 @@ import logging import re +from typing import Dict from command.platform_command_wrapper import PlatformCommandWrapper -from util.common_utils import get_oneagentctl_path, get_installers +from util.common_utils import get_installers, get_oneagentctl_path from util.test_data_types import DeploymentPlatform, PlatformCollection from util.test_helpers import ( check_agent_state, perform_operation_on_platforms, - set_installer_download_params, run_deployment, + set_installer_download_params, ) + def _get_versions_for_platforms(platforms: PlatformCollection, latest: bool) -> dict[DeploymentPlatform, str]: versions: Dict[DeploymentPlatform, str] = {} - for platform, _ in platforms.items(): + for platform, hosts in platforms.items(): installers = get_installers(platform.system(), platform.arch()) versioned_installer = installers[-1 if latest else 0] versions[platform] = re.search(r"\d.\d+.\d+.\d+-\d+", str(versioned_installer)).group() return versions + def _check_agent_version( - platform: DeploymentPlatform, address: str, wrapper: PlatformCommandWrapper, versions: dict[DeploymentPlatform, str] + platform: DeploymentPlatform, + address: str, + wrapper: PlatformCommandWrapper, + versions: dict[DeploymentPlatform, str], ) -> None: installed_version = wrapper.run_command(platform, address, f"{get_oneagentctl_path(platform)}", "--version") assert installed_version.stdout.strip() == versions[platform] @@ -53,4 +59,4 @@ def test_upgrade(runner, configurator, platforms, wrapper, installer_server_url) logging.info("Check if agent has proper version") new_versions = _get_versions_for_platforms(platforms, True) - perform_operation_on_platforms(platforms, _check_agent_version, wrapper, new_versions) \ No newline at end of file + perform_operation_on_platforms(platforms, _check_agent_version, wrapper, new_versions) diff --git a/roles/oneagent/tests/util/common_utils.py b/roles/oneagent/tests/util/common_utils.py index c3fc940..59312f8 100644 --- a/roles/oneagent/tests/util/common_utils.py +++ b/roles/oneagent/tests/util/common_utils.py @@ -2,15 +2,14 @@ import os import shutil from pathlib import Path -from typing import Any +from typing import Any, Dict import yaml - from util.constants.common_constants import ( - TEST_DIRECTORY, INSTALLER_PARTIAL_NAME, INSTALLER_SYSTEM_NAME_TYPE_MAP, INSTALLERS_DIRECTORY, + TEST_DIRECTORY, ) from util.constants.unix_constants import UNIX_ONEAGENTCTL_PATH from util.constants.windows_constants import WINDOWS_ONEAGENTCTL_PATH @@ -58,13 +57,14 @@ def _get_platform_by_installer(installer: Path) -> DeploymentPlatform: if platform.arch() in name and platform.system() in name: return platform - # Special handling for Linux_x86 and Windows as the installer does not contain architecture in its name + # Special handling for Linux_x86 and Windows as the installer does not + # contain architecture in its name if DeploymentPlatform.WINDOWS_X86.system() in name: return DeploymentPlatform.WINDOWS_X86 return DeploymentPlatform.LINUX_X86 -def _get_available_installers() -> dict[DeploymentPlatform, list[Path]]: +def _get_available_installers() -> Dict[DeploymentPlatform, list[Path]]: installers: dict[DeploymentPlatform, list[Path]] = {k: [] for k in DeploymentPlatform} for installer in sorted(INSTALLERS_DIRECTORY.glob(f"{INSTALLER_PARTIAL_NAME}*")): platform = _get_platform_by_installer(installer) @@ -74,7 +74,8 @@ def _get_available_installers() -> dict[DeploymentPlatform, list[Path]]: def get_installers(system: str, arch: str, version: str = "", include_paths: bool = False) -> list[Path | str]: try: - # Special handling for mocking server behavior as URL for Linux installers contains "unix" instead of linux + # Special handling for mocking server behavior as URL for Linux + # installers contains "unix" instead of linux system = INSTALLER_SYSTEM_NAME_TYPE_MAP[system] platform_installers = _get_available_installers()[DeploymentPlatform.from_system_and_arch(system, arch)] installers = platform_installers if include_paths else [ins.name for ins in platform_installers] @@ -85,5 +86,5 @@ def get_installers(system: str, arch: str, version: str = "", include_paths: boo return [installer for installer in installers if version in str(installer)] except Exception as ex: - logging.error(f"Failed to get installer for {system}_{arch} in {version} version: {ex}") + logging.error("Failed to get installer for %s_%s in %s version: %s", system, arch, version, ex) return [] diff --git a/roles/oneagent/tests/util/constants/common_constants.py b/roles/oneagent/tests/util/constants/common_constants.py index 3f9d0d4..d80e10a 100644 --- a/roles/oneagent/tests/util/constants/common_constants.py +++ b/roles/oneagent/tests/util/constants/common_constants.py @@ -3,12 +3,12 @@ # TODO: is cwd() correct? COMPONENT_TEST_BASE = Path().cwd() / "test_dir" -TEST_DIRECTORY = COMPONENT_TEST_BASE / "working_dir" -RESOURCES_DIRECTORY = Path(__file__).resolve().parent.parent.parent / "resources" INSTALLERS_DIRECTORY = COMPONENT_TEST_BASE / "installers" -SERVER_DIRECTORY = COMPONENT_TEST_BASE / "server" +RESOURCES_DIRECTORY = Path(__file__).resolve().parent.parent.parent / "resources" INSTALLERS_RESOURCE_DIR = RESOURCES_DIRECTORY / "installers" LOG_DIRECTORY = COMPONENT_TEST_BASE / "logs" +SERVER_DIRECTORY = COMPONENT_TEST_BASE / "server" +TEST_DIRECTORY = COMPONENT_TEST_BASE / "working_dir" INSTALLER_CERTIFICATE_FILE_NAME = "dt-root.cert.pem" INSTALLER_PRIVATE_KEY_FILE_NAME = "dt-root.key" @@ -20,6 +20,7 @@ INSTALLER_SERVER_TOKEN = "abcdefghijk1234567890" + class InstallerVersion(Enum): OLD = "1.199.0.20241008-150308" LATEST = "1.300.0.20241008-150308" diff --git a/roles/oneagent/tests/util/constants/unix_constants.py b/roles/oneagent/tests/util/constants/unix_constants.py index 5079c25..24306a3 100644 --- a/roles/oneagent/tests/util/constants/unix_constants.py +++ b/roles/oneagent/tests/util/constants/unix_constants.py @@ -1,6 +1,6 @@ from pathlib import Path -UNIX_ONEAGENTCTL_BIN_NAME = "oneagentctl" -UNIX_DEFAULT_INSTALL_PATH = Path("/opt") / "dynatrace" / "oneagent" UNIX_DEFAULT_DOWNLOAD_PATH = Path("/tmp") +UNIX_DEFAULT_INSTALL_PATH = Path("/opt") / "dynatrace" / "oneagent" +UNIX_ONEAGENTCTL_BIN_NAME = "oneagentctl" UNIX_ONEAGENTCTL_PATH = UNIX_DEFAULT_INSTALL_PATH / "agent" / "tools" / UNIX_ONEAGENTCTL_BIN_NAME diff --git a/roles/oneagent/tests/util/constants/windows_constants.py b/roles/oneagent/tests/util/constants/windows_constants.py index be3f8a2..9322c37 100644 --- a/roles/oneagent/tests/util/constants/windows_constants.py +++ b/roles/oneagent/tests/util/constants/windows_constants.py @@ -1,6 +1,6 @@ from pathlib import Path -WINDOWS_ONEAGENTCTL_BIN_NAME = "oneagentctl.exe" -WINDOWS_DEFAULT_INSTALL_PATH = Path("C:\\Program Files") / "dynatrace" / "oneagent" WINDOWS_DEFAULT_DOWNLOAD_PATH = Path("%TEMP%") +WINDOWS_DEFAULT_INSTALL_PATH = Path("C:\\Program Files") / "dynatrace" / "oneagent" +WINDOWS_ONEAGENTCTL_BIN_NAME = "oneagentctl.exe" WINDOWS_ONEAGENTCTL_PATH = WINDOWS_DEFAULT_INSTALL_PATH / "agent" / "tools" / WINDOWS_ONEAGENTCTL_BIN_NAME diff --git a/roles/oneagent/tests/util/installer_provider.py b/roles/oneagent/tests/util/installer_provider.py index 98d1074..125ffa7 100644 --- a/roles/oneagent/tests/util/installer_provider.py +++ b/roles/oneagent/tests/util/installer_provider.py @@ -1,130 +1,153 @@ -import logging -import subprocess - -import requests - -from pathlib import Path - -from util.test_data_types import DeploymentPlatform, PlatformCollection -from util.ssl_certificate_generator import SSLCertificateGenerator -from util.constants.common_constants import (INSTALLERS_RESOURCE_DIR, INSTALLERS_DIRECTORY, - INSTALLER_PRIVATE_KEY_FILE_NAME, INSTALLER_CERTIFICATE_FILE_NAME, - InstallerVersion) - - -def get_file_content(path: Path) -> list[str]: - with path.open("r") as f: - return f.readlines() - - -def replace_tag(source: list[str], old: str, new: str) -> list[str]: - return [line.replace(old, new) for line in source] - - -def sign_installer(installer: list[str]) -> list[str]: - cmd = ["openssl", "cms", "-sign", - "-signer", f"{INSTALLERS_DIRECTORY / INSTALLER_CERTIFICATE_FILE_NAME}", - "-inkey", f"{INSTALLERS_DIRECTORY / INSTALLER_PRIVATE_KEY_FILE_NAME}"] - - proc = subprocess.run(cmd, input=f"{''.join(installer)}", encoding="utf-8", stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - if proc.returncode != 0: - logging.error(f"Failed to sign installer: {proc.stdout}") - return [] - - signed_installer = proc.stdout.splitlines() - delimiter = next(l for l in signed_installer if l.startswith("----")) - index = signed_installer.index(delimiter) - signed_installer = signed_installer[index + 1:] - - custom_delimiter = "----SIGNED-INSTALLER" - return [f"{l}\n" if not l.startswith(delimiter) else f"{l.replace(delimiter, custom_delimiter)}\n" for l in signed_installer] - - -def generate_installers() -> bool: - uninstall_template = get_file_content(INSTALLERS_RESOURCE_DIR / "uninstall.sh") - uninstall_code = replace_tag(uninstall_template, "$", r"\$") - - oneagentctl_template = get_file_content(INSTALLERS_RESOURCE_DIR / "oneagentctl.sh") - oneagentctl_code = replace_tag(oneagentctl_template, "$", r"\$") - - installer_partial_name = "Dynatrace-OneAgent-Linux" - installer_template = get_file_content(INSTALLERS_RESOURCE_DIR / f"{installer_partial_name}.sh") - installer_template = replace_tag(installer_template, "##UNINSTALL_CODE##", "".join(uninstall_code)) - installer_template = replace_tag(installer_template, "##ONEAGENTCTL_CODE##", "".join(oneagentctl_code)) - - generator = SSLCertificateGenerator( - country_name="US", - state_name="California", - locality_name="San Francisco", - organization_name="Dynatrace", - common_name="127.0.0.1" - ) - generator.generate_and_save(f"{INSTALLERS_DIRECTORY / INSTALLER_PRIVATE_KEY_FILE_NAME}", - f"{INSTALLERS_DIRECTORY / INSTALLER_CERTIFICATE_FILE_NAME}") - - # REMOVE INSTALLER VERSION - for version in InstallerVersion: - installer_code = replace_tag(installer_template, "##VERSION##", version.value) - installer_code = sign_installer(installer_code) - if not installer_code: - return False - with open(INSTALLERS_DIRECTORY / f"{installer_partial_name}-{version.value}.sh", "w") as f: - f.writelines(installer_code) - - return True - - -def get_installers_versions_from_tenant(tenant: str, tenant_token: str, system_family: str) -> list[str]: - url = f"{tenant}/api/v1/deployment/installer/agent/versions/{system_family}/default" - headers = {"accept": "application/json", "Authorization": f"Api-Token {tenant_token}"} - - resp = requests.get(url, headers=headers) - - try: - versions = resp.json()["availableVersions"] - if len(versions) < 2: - logging.error(f"List of available installers is too short: {versions}") - else: - return [versions[0], versions[-1]] - except KeyError: - logging.error(f"Failed to get list of installer versions: {resp.content}") - return [] - - -def download_and_save(path: Path, url: str, headers: dict[str, str]) -> bool: - resp = requests.get(url, headers=headers) - - if not resp.ok: - logging.error(f"Failed to download file {path}: {resp.text}") - return False - - with path.open("wb") as f: - f.write(resp.content) - return True - - -def download_signature(url: str) -> bool: - path = INSTALLERS_DIRECTORY / INSTALLER_CERTIFICATE_FILE_NAME - return download_and_save(path, url, {}) - - -def download_installer(tenant, tenant_token, version: str, platform: DeploymentPlatform) -> bool: - family = platform.family() - url = f"{tenant}/api/v1/deployment/installer/agent/{family}/default/version/{version}" - headers = {"accept": "application/octet-stream", "Authorization": f"Api-Token {tenant_token}"} - - ext = "exe" if family == "windows" else "sh" - path = INSTALLERS_DIRECTORY / f"Dynatrace-OneAgent-{family}_{platform.arch()}-{version}.{ext}" - - return download_and_save(path, url, headers) - - -def download_installers(tenant, tenant_token, platforms: PlatformCollection) -> bool: - for platform, _ in platforms.items(): - versions = get_installers_versions_from_tenant(tenant, tenant_token, platform.family()) - if not versions: - return False - for version in versions: - if not download_installer(tenant, tenant_token, version, platform): - return False - return True +import logging +import subprocess + +import requests + +from pathlib import Path + +from util.test_data_types import DeploymentPlatform, PlatformCollection +from util.ssl_certificate_generator import SSLCertificateGenerator +from util.constants.common_constants import ( + INSTALLERS_RESOURCE_DIR, + INSTALLERS_DIRECTORY, + INSTALLER_PRIVATE_KEY_FILE_NAME, + INSTALLER_CERTIFICATE_FILE_NAME, + InstallerVersion, +) + + +def get_file_content(path: Path) -> list[str]: + with path.open("r") as f: + return f.readlines() + + +def replace_tag(source: list[str], old: str, new: str) -> list[str]: + return [line.replace(old, new) for line in source] + + +def sign_installer(installer: list[str]) -> list[str]: + cmd = [ + "openssl", + "cms", + "-sign", + "-signer", + f"{INSTALLERS_DIRECTORY / INSTALLER_CERTIFICATE_FILE_NAME}", + "-inkey", + f"{INSTALLERS_DIRECTORY / INSTALLER_PRIVATE_KEY_FILE_NAME}", + ] + + proc = subprocess.run( + cmd, + input=f"{''.join(installer)}", + encoding="utf-8", + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=False, + ) + if proc.returncode != 0: + logging.error("Failed to sign installer: %s") + return [] + + signed_installer = proc.stdout.splitlines() + delimiter = next(l for l in signed_installer if l.startswith("----")) + index = signed_installer.index(delimiter) + signed_installer = signed_installer[index + 1 :] + + custom_delimiter = "----SIGNED-INSTALLER" + + return [ + f"{l}\n" if not l.startswith(delimiter) else f"{l.replace(delimiter, custom_delimiter)}\n" + for l in signed_installer + ] + + +def generate_installers() -> bool: + uninstall_template = get_file_content(INSTALLERS_RESOURCE_DIR / "uninstall.sh") + uninstall_code = replace_tag(uninstall_template, "$", r"\$") + + oneagentctl_template = get_file_content(INSTALLERS_RESOURCE_DIR / "oneagentctl.sh") + oneagentctl_code = replace_tag(oneagentctl_template, "$", r"\$") + + installer_partial_name = "Dynatrace-OneAgent-Linux" + installer_template = get_file_content(INSTALLERS_RESOURCE_DIR / f"{installer_partial_name}.sh") + installer_template = replace_tag(installer_template, "##UNINSTALL_CODE##", "".join(uninstall_code)) + installer_template = replace_tag(installer_template, "##ONEAGENTCTL_CODE##", "".join(oneagentctl_code)) + + generator = SSLCertificateGenerator( + country_name="US", + state_name="California", + locality_name="San Francisco", + organization_name="Dynatrace", + common_name="127.0.0.1", + ) + generator.generate_and_save( + f"{INSTALLERS_DIRECTORY /INSTALLER_PRIVATE_KEY_FILE_NAME}", + f"{INSTALLERS_DIRECTORY /INSTALLER_CERTIFICATE_FILE_NAME}", + ) + + # REMOVE INSTALLER VERSION + for version in InstallerVersion: + installer_code = replace_tag(installer_template, "##VERSION##", version.value) + installer_code = sign_installer(installer_code) + if not installer_code: + return False + with open(INSTALLERS_DIRECTORY / f"{installer_partial_name}-{version.value}.sh", "w") as f: + f.writelines(installer_code) + + return True + + +def get_installers_versions_from_tenant(tenant: str, tenant_token: str, system_family: str) -> list[str]: + url = f"{tenant}/api/v1/deployment/installer/agent/versions/{system_family}/default" + headers = {"accept": "application/json", "Authorization": f"Api-Token {tenant_token}"} + + resp = requests.get(url, headers=headers) + + try: + versions = resp.json()["availableVersions"] + if len(versions) < 2: + logging.error("List of available installers is too short: %s", versions) + else: + return [versions[0], versions[-1]] + except KeyError: + logging.error("Failed to get list of installer versions: %s", resp.content) + return [] + + +def download_and_save(path: Path, url: str, headers: dict[str, str]) -> bool: + resp = requests.get(url, headers=headers) + + if not resp.ok: + logging.error("Failed to download file %s: %s", path, resp.text) + return False + + with path.open("wb") as f: + f.write(resp.content) + return True + + +def download_signature(url: str) -> bool: + path = INSTALLERS_DIRECTORY / INSTALLER_CERTIFICATE_FILE_NAME + return download_and_save(path, url, {}) + + +def download_installer(tenant, tenant_token, version: str, platform: DeploymentPlatform) -> bool: + family = platform.family() + url = f"{tenant}/api/v1/deployment/installer/agent/{family}/default/version/{version}" + headers = {"accept": "application/octet-stream", "Authorization": f"Api-Token {tenant_token}"} + + ext = "exe" if family == "windows" else "sh" + path = INSTALLERS_DIRECTORY / f"Dynatrace-OneAgent-{family}_{platform.arch()}-{version}.{ext}" + + return download_and_save(path, url, headers) + + +def download_installers(tenant, tenant_token, platforms: PlatformCollection) -> bool: + for platform, hosts in platforms.items(): + versions = get_installers_versions_from_tenant(tenant, tenant_token, platform.family()) + if not versions: + return False + for version in versions: + if not download_installer(tenant, tenant_token, version, platform): + return False + return True diff --git a/roles/oneagent/tests/util/ssl_certificate_generator.py b/roles/oneagent/tests/util/ssl_certificate_generator.py index 941b209..de47092 100644 --- a/roles/oneagent/tests/util/ssl_certificate_generator.py +++ b/roles/oneagent/tests/util/ssl_certificate_generator.py @@ -1,13 +1,12 @@ import ipaddress import logging import uuid +from datetime import datetime, timedelta from cryptography import x509 -from cryptography.x509.oid import NameOID -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives import serialization -from datetime import datetime, timedelta +from cryptography.x509.oid import NameOID class SSLCertificateGenerator: @@ -28,20 +27,28 @@ def _generate_key_pair(self) -> (rsa.RSAPrivateKey, rsa.RSAPublicKey): def _generate_certificate(self, private_key, public_key) -> x509.Certificate: builder = x509.CertificateBuilder() - builder = builder.subject_name(x509.Name([ - x509.NameAttribute(NameOID.COUNTRY_NAME, self.country_name), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, self.state_name), - x509.NameAttribute(NameOID.LOCALITY_NAME, self.locality_name), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, self.organization_name), - x509.NameAttribute(NameOID.COMMON_NAME, self.common_name), - ])) - builder = builder.issuer_name(x509.Name([ - x509.NameAttribute(NameOID.COUNTRY_NAME, self.country_name), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, self.state_name), - x509.NameAttribute(NameOID.LOCALITY_NAME, self.locality_name), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, self.organization_name), - x509.NameAttribute(NameOID.COMMON_NAME, self.common_name), - ])) + builder = builder.subject_name( + x509.Name( + [ + x509.NameAttribute(NameOID.COUNTRY_NAME, self.country_name), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, self.state_name), + x509.NameAttribute(NameOID.LOCALITY_NAME, self.locality_name), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, self.organization_name), + x509.NameAttribute(NameOID.COMMON_NAME, self.common_name), + ] + ) + ) + builder = builder.issuer_name( + x509.Name( + [ + x509.NameAttribute(NameOID.COUNTRY_NAME, self.country_name), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, self.state_name), + x509.NameAttribute(NameOID.LOCALITY_NAME, self.locality_name), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, self.organization_name), + x509.NameAttribute(NameOID.COMMON_NAME, self.common_name), + ] + ) + ) builder = builder.not_valid_before(datetime.utcnow()) builder = builder.not_valid_after(datetime.utcnow() + timedelta(days=self.validity_days)) builder = builder.serial_number(int(uuid.uuid4())) @@ -58,11 +65,13 @@ def _generate_certificate(self, private_key, public_key) -> x509.Certificate: def _save_private_key(self, filename, private_key) -> None: with open(filename, "wb") as f: - f.write(private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), - )) + f.write( + private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + ) def _save_certificate(self, filename, certificate) -> None: with open(filename, "wb") as f: diff --git a/roles/oneagent/tests/util/test_data_types.py b/roles/oneagent/tests/util/test_data_types.py index 348d3ba..25e66b7 100644 --- a/roles/oneagent/tests/util/test_data_types.py +++ b/roles/oneagent/tests/util/test_data_types.py @@ -27,7 +27,7 @@ def arch(self) -> str: return str(self.value).split("_")[1] def system(self) -> str: - return str(self.value).split("_")[0] + return str(self.value).split("_", maxsplit=1)[0] @staticmethod def from_str(param: str) -> "DeploymentPlatform": diff --git a/roles/oneagent/tests/util/test_helpers.py b/roles/oneagent/tests/util/test_helpers.py index a2195d5..93cc1e4 100644 --- a/roles/oneagent/tests/util/test_helpers.py +++ b/roles/oneagent/tests/util/test_helpers.py @@ -1,15 +1,19 @@ import functools import logging from pathlib import Path -from typing import Callable, Any - -from command.platform_command_wrapper import PlatformCommandWrapper +from typing import Any, Callable from ansible.config import AnsibleConfig from ansible.runner import AnsibleRunner +from command.platform_command_wrapper import PlatformCommandWrapper from util.common_utils import get_oneagentctl_path, get_platform_argument -from util.constants.common_constants import (INSTALLER_PARTIAL_NAME, INSTALLER_SERVER_TOKEN, INSTALLER_CERTIFICATE_FILE_NAME, - SERVER_DIRECTORY, SERVER_CERTIFICATE_FILE_NAME) +from util.constants.common_constants import ( + INSTALLER_CERTIFICATE_FILE_NAME, + INSTALLER_PARTIAL_NAME, + INSTALLER_SERVER_TOKEN, + SERVER_CERTIFICATE_FILE_NAME, + SERVER_DIRECTORY, +) from util.test_data_types import DeploymentPlatform, DeploymentResult, PlatformCollection CallableOperation = Callable[[DeploymentPlatform, str, Any], None] @@ -30,7 +34,7 @@ def params_wrapper(*args, **kwargs): config.set_deployment_hosts(family) func(*args, **kwargs) else: - logging.info(f"Skipping test for specified platform") + logging.info("Skipping test for specified platform") return params_wrapper @@ -44,7 +48,9 @@ def perform_operation_on_platforms(platforms: PlatformCollection, operation: Cal def set_ca_cert_download_params(config: AnsibleConfig, installer_server_url: str) -> None: - config.set_common_parameter(config.CA_CERT_DOWNLOAD_URL_KEY, f"{installer_server_url}/{INSTALLER_CERTIFICATE_FILE_NAME}") + config.set_common_parameter( + config.CA_CERT_DOWNLOAD_URL_KEY, f"{installer_server_url}/{INSTALLER_CERTIFICATE_FILE_NAME}" + ) config.set_common_parameter(config.CA_CERT_DOWNLOAD_CERT_KEY, f"{SERVER_DIRECTORY / SERVER_CERTIFICATE_FILE_NAME}") config.set_common_parameter(config.FORCE_CERT_DOWNLOAD_KEY, True) @@ -52,7 +58,9 @@ def set_ca_cert_download_params(config: AnsibleConfig, installer_server_url: str def set_installer_download_params(config: AnsibleConfig, installer_server_url: str) -> None: config.set_common_parameter(config.ENVIRONMENT_URL_KEY, installer_server_url) config.set_common_parameter(config.PAAS_TOKEN_KEY, INSTALLER_SERVER_TOKEN) - config.set_common_parameter(config.INSTALLER_DOWNLOAD_CERT_KEY, f"{SERVER_DIRECTORY / SERVER_CERTIFICATE_FILE_NAME}") + config.set_common_parameter( + config.INSTALLER_DOWNLOAD_CERT_KEY, f"{SERVER_DIRECTORY / SERVER_CERTIFICATE_FILE_NAME}" + ) for platform in DeploymentPlatform: config.set_platform_parameter(platform, config.INSTALLER_ARCH_KEY, platform.arch()) @@ -63,7 +71,7 @@ def run_deployment(runner: AnsibleRunner, ignore_errors: bool = False) -> Deploy results = runner.run_deployment() logging.info("Deployment finished") for result in results: - logging.debug(f"Exit code: {result.returncode}\nOutput: {result.stdout}, Error: {result.stderr}") + logging.debug("Exit code: %s\nOutput: %s, Error: %s", result.returncode, result.stdout, result.stderr) if not ignore_errors: logging.info("Check exit codes")