diff --git a/environments/etc/installer-manifests/site.pp b/environments/etc/installer-manifests/site.pp new file mode 100644 index 00000000..392773d6 --- /dev/null +++ b/environments/etc/installer-manifests/site.pp @@ -0,0 +1,10 @@ +node default { + class { 'datadog_agent': + api_key => 'somenonnullapikeythats32charlong', + manage_install => false, + datadog_installer_enabled => true, + apm_instrumentation_enabled => 'host', + apm_instrumentation_libraries => ['java:1', 'python:2'], + remote_updates => false, + } +} diff --git a/kitchen.yml b/kitchen.yml index 86ce2168..bcee3051 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -117,3 +117,15 @@ suites: TARGET_PORT: 2222 LOGIN_USER: root LOGIN_PASSWORD: puppet + - name: dd-installer + manifests: init.pp + provisioner: + manifests_path: environments/etc/installer-manifests + verifier: + default_pattern: true + additional_install_commmand: source /etc/profile.d/rvm.sh + env_vars: + TARGET_HOST: 127.0.0.1 + TARGET_PORT: 2222 + LOGIN_USER: root + LOGIN_PASSWORD: puppet \ No newline at end of file diff --git a/manifests/init.pp b/manifests/init.pp index 579c5113..b75de9ac 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -242,6 +242,20 @@ # # $windows_ddagentuser_password # (Windows only) The password used to register the service`. +# $datadog_installer_enabled +# Boolean to enable or disable the Datadog installer. +# Boolean. Default: undef (false) +# $apm_instrumentation_enabled +# Configure APM instrumentation. Possible values are: +# - host: Both the Agent and your services are running on a host. +# - docker: The Agent and your services are running in separate Docker containers on the same host. +# - all: Supports all the previous scenarios for host and docker at the same time. +# $apm_instrumentation_libraries +# List of APM libraries to install. If not defined and APM instrumentation is set, +# the default libraries are pinned: ['java:1', 'python:2', 'js:5', 'dotnet:3', 'ruby:2'] +# $remote_updates +# Boolean to enable or disable Agent remote updates. +# Boolean. Default: false # # Sample Usage: # @@ -257,7 +271,7 @@ # } # # -class datadog_agent( +class datadog_agent ( String $dd_url = '', String $datadog_site = $datadog_agent::params::datadog_site, String $host = '', @@ -285,6 +299,7 @@ Boolean $manage_repo = true, Boolean $manage_dogapi_gem = true, Boolean $manage_install = true, + Optional[Boolean] $datadog_installer_enabled = undef, $hostname_extraction_regex = undef, Boolean $hostname_fqdn = false, Variant[Stdlib::Port, Pattern[/^\d*$/]] $dogstatsd_port = 8125, @@ -368,6 +383,9 @@ Boolean $windows_npm_install = false, Optional[String] $windows_ddagentuser_name = undef, Optional[String] $windows_ddagentuser_password = undef, + Boolean $remote_updates = $datadog_agent::params::remote_updates, + Optional[Enum['host', 'docker', 'all']] $apm_instrumentation_enabled = undef, + Optional[Array[String]] $apm_instrumentation_libraries = undef, ) inherits datadog_agent::params { #In this regex, version '1:6.15.0~rc.1-1' would match as $1='1:', $2='6', $3='15', $4='0', $5='~rc.1', $6='1' @@ -377,6 +395,7 @@ fail('Provided and deduced agent_major_version don\'t match') } $_agent_minor_version = 0 + $3 + $_agent_patch_version = 0 + $4 } elsif $agent_major_version != undef { $_agent_major_version = $agent_major_version } else { @@ -441,96 +460,176 @@ default: { $_loglevel = 'INFO' } } - # Install agent - if $manage_install { + if $datadog_installer_enabled { + # If instrumentation is enabled and the libraries are not set, default to pinned latest versions + # Else, if user wants to install libraries without enabling instrumentation, use the provided libraries + if $apm_instrumentation_enabled and ! $apm_instrumentation_libraries { + $apm_instrumentation_libraries_str = join(['java:1', 'python:2', 'js:5', 'dotnet:3', 'ruby:2'], ',') + } elsif $apm_instrumentation_libraries { + $apm_instrumentation_libraries_str = join($apm_instrumentation_libraries, ',') + } else { + $apm_instrumentation_libraries_str = '' + } + # Agent version handling: the installer expects DD_AGENT_MINOR_VERSION to include the patch version. + # $_agent_minor_version is the minor version without the patch version. + # We need to add the patch version to the minor version to get the full version. + # If minor and patch version were not extracted (e.g. user is simply providing agent_major_version), we use an empty string for the minor version. + if $_agent_minor_version != undef and $_agent_patch_version != undef { + $_agent_minor_version_full = "${_agent_minor_version}.${_agent_patch_version}" + } else { + $_agent_minor_version_full = '' + } case $facts['os']['name'] { - 'Ubuntu','Debian','Raspbian' : { - if $use_apt_backup_keyserver != undef or $apt_backup_keyserver != undef or $apt_keyserver != undef { - notify { 'apt keyserver arguments deprecation': - message => '$use_apt_backup_keyserver, $apt_backup_keyserver and $apt_keyserver are deprecated since version 3.13.0', - loglevel => 'warning', - } - } - class { 'datadog_agent::ubuntu': - agent_major_version => $_agent_major_version, - agent_version => $agent_full_version, - agent_flavor => $agent_flavor, - agent_repo_uri => $agent_repo_uri, - release => $apt_release, - skip_apt_key_trusting => $skip_apt_key_trusting, + 'Ubuntu','Debian','Raspbian': { + class { 'datadog_agent::ubuntu_installer': + api_key => $api_key, + datadog_site => $datadog_site, + agent_major_version => $_agent_major_version, + agent_minor_version => $_agent_minor_version_full, + manage_agent_install => $manage_install, + installer_repo_uri => $agent_repo_uri, + release => $apt_release, + skip_apt_key_trusting => $skip_apt_key_trusting, + apm_instrumentation_enabled => $apm_instrumentation_enabled, + apm_instrumentation_libraries_str => $apm_instrumentation_libraries_str, + remote_updates => $remote_updates, } } 'RedHat','CentOS','Fedora','Amazon','Scientific','OracleLinux','AlmaLinux','Rocky' : { - class { 'datadog_agent::redhat': - agent_major_version => $_agent_major_version, - agent_flavor => $agent_flavor, - agent_repo_uri => $agent_repo_uri, - manage_repo => $manage_repo, - agent_version => $agent_full_version, - rpm_repo_gpgcheck => $rpm_repo_gpgcheck, - } - } - 'Windows' : { - class { 'datadog_agent::windows' : - agent_major_version => $_agent_major_version, - agent_repo_uri => $agent_repo_uri, - agent_version => $agent_full_version, - msi_location => $win_msi_location, - api_key => $api_key, - hostname => $host, - tags => $local_tags, - ensure => $win_ensure, - npm_install => $windows_npm_install, - ddagentuser_name => $windows_ddagentuser_name, - ddagentuser_password => $windows_ddagentuser_password, - } - if ($win_ensure == absent) { - return() #Config files will remain unchanged on uninstall + class { 'datadog_agent::redhat_installer': + api_key => $api_key, + datadog_site => $datadog_site, + agent_major_version => $_agent_major_version, + agent_minor_version => $_agent_minor_version_full, + installer_repo_uri => $agent_repo_uri, + rpm_repo_gpgcheck => $rpm_repo_gpgcheck, + apm_instrumentation_enabled => $apm_instrumentation_enabled, + apm_instrumentation_libraries_str => $apm_instrumentation_libraries_str, + remote_updates => $remote_updates, } } 'OpenSuSE', 'SLES' : { - class { 'datadog_agent::suse' : - agent_major_version => $_agent_major_version, - agent_flavor => $agent_flavor, - agent_repo_uri => $agent_repo_uri, - agent_version => $agent_full_version, - rpm_repo_gpgcheck => $rpm_repo_gpgcheck, + class { 'datadog_agent::suse_installer': + api_key => $api_key, + datadog_site => $datadog_site, + agent_major_version => $_agent_major_version, + agent_minor_version => $_agent_minor_version_full, + installer_repo_uri => $agent_repo_uri, + rpm_repo_gpgcheck => $rpm_repo_gpgcheck, + apm_instrumentation_enabled => $apm_instrumentation_enabled, + apm_instrumentation_libraries_str => $apm_instrumentation_libraries_str, + remote_updates => $remote_updates, } } - default: { fail("Class[datadog_agent]: Unsupported operatingsystem: ${facts['os']['name']}") } + default: { fail("Class[datadog_agent::installer]: Unsupported operatingsystem: ${facts['os']['name']}") } } - } else { - if ! defined(Package[$agent_flavor]) { - package { $agent_flavor: - ensure => present, - source => 'Agent installation not managed by Puppet, make sure the Agent is installed beforehand.', + } + + # If the agent is managed by the installer, we don't need to manage the agent installation + $_agent_managed_by_installer = ($datadog_installer_enabled and $remote_updates) + + # Install agent + if ! $_agent_managed_by_installer { + if $manage_install { + case $facts['os']['name'] { + 'Ubuntu','Debian','Raspbian' : { + if $use_apt_backup_keyserver != undef or $apt_backup_keyserver != undef or $apt_keyserver != undef { + notify { 'apt keyserver arguments deprecation': + message => '$use_apt_backup_keyserver, $apt_backup_keyserver and $apt_keyserver are deprecated since version 3.13.0', + loglevel => 'warning', + } + } + class { 'datadog_agent::ubuntu': + agent_major_version => $_agent_major_version, + agent_version => $agent_full_version, + agent_flavor => $agent_flavor, + agent_repo_uri => $agent_repo_uri, + release => $apt_release, + skip_apt_key_trusting => $skip_apt_key_trusting, + } + } + 'RedHat','CentOS','Fedora','Amazon','Scientific','OracleLinux','AlmaLinux','Rocky' : { + class { 'datadog_agent::redhat': + agent_major_version => $_agent_major_version, + agent_flavor => $agent_flavor, + agent_repo_uri => $agent_repo_uri, + manage_repo => $manage_repo, + agent_version => $agent_full_version, + rpm_repo_gpgcheck => $rpm_repo_gpgcheck, + } + } + 'Windows' : { + class { 'datadog_agent::windows' : + agent_major_version => $_agent_major_version, + agent_repo_uri => $agent_repo_uri, + agent_version => $agent_full_version, + msi_location => $win_msi_location, + api_key => $api_key, + hostname => $host, + tags => $local_tags, + ensure => $win_ensure, + npm_install => $windows_npm_install, + ddagentuser_name => $windows_ddagentuser_name, + ddagentuser_password => $windows_ddagentuser_password, + } + if ($win_ensure == absent) { + return() #Config files will remain unchanged on uninstall + } + } + 'OpenSuSE', 'SLES' : { + class { 'datadog_agent::suse' : + agent_major_version => $_agent_major_version, + agent_flavor => $agent_flavor, + agent_repo_uri => $agent_repo_uri, + agent_version => $agent_full_version, + rpm_repo_gpgcheck => $rpm_repo_gpgcheck, + } + } + default: { fail("Class[datadog_agent]: Unsupported operatingsystem: ${facts['os']['name']}") } + } + } else { + if ! defined(Package[$agent_flavor]) { + package { $agent_flavor: + ensure => present, + source => 'Agent installation not managed by Puppet, make sure the Agent is installed beforehand.', + } } } } # Declare service - class { 'datadog_agent::service' : - agent_flavor => $agent_flavor, - service_ensure => $service_ensure, - service_enable => $service_enable, - service_provider => $service_provider, - } + if ! $_agent_managed_by_installer { + class { 'datadog_agent::service' : + agent_flavor => $agent_flavor, + service_ensure => $service_ensure, + service_enable => $service_enable, + service_provider => $service_provider, + } + if ($facts['os']['name'] != 'Windows') { + if ($dd_groups) { + user { $dd_user: + groups => $dd_groups, + notify => Service[$datadog_agent::params::service_name], + } + } - if ($facts['os']['name'] != 'Windows') { - if ($dd_groups) { - user { $dd_user: - groups => $dd_groups, - notify => Service[$datadog_agent::params::service_name], + # required by reports even in agent5 scenario + file { '/etc/datadog-agent': + ensure => directory, + owner => $dd_user, + group => $dd_group, + mode => $datadog_agent::params::permissions_directory, + require => Package[$agent_flavor], } } - - # required by reports even in agent5 scenario + } else { + # required to manage config and install info files even with installer file { '/etc/datadog-agent': ensure => directory, owner => $dd_user, group => $dd_group, mode => $datadog_agent::params::permissions_directory, - require => Package[$agent_flavor], + require => Package['datadog-installer'], } } @@ -766,7 +865,10 @@ force => $conf_dir_purge, owner => $dd_user, group => $dd_group, - notify => Service[$datadog_agent::params::service_name] + } + + if ! $_agent_managed_by_installer { + File[$_conf_dir] ~> Service[$datadog_agent::params::service_name] } $_local_tags = datadog_agent::tag6($local_tags, false, undef) @@ -788,12 +890,12 @@ 'dogstatsd_non_local_traffic' => $non_local_traffic, 'log_file' => $agent_log_file, 'log_level' => $log_level, + 'remote_updates' => $remote_updates, 'tags' => unique(flatten(union($_local_tags, $_facts_tags, $_trusted_facts_tags))), } $agent_config = deep_merge($_agent_config, $extra_config) - if ($facts['os']['name'] == 'Windows') { @@ -827,9 +929,11 @@ mode => '0640', content => template('datadog_agent/datadog.yaml.erb'), show_diff => false, - notify => Service[$datadog_agent::params::service_name], require => File['/etc/datadog-agent'], } + if ! $_agent_managed_by_installer { + File['/etc/datadog-agent/datadog.yaml'] ~> Service[$datadog_agent::params::service_name] + } file { '/etc/datadog-agent/install_info': owner => $dd_user, @@ -838,7 +942,6 @@ content => template('datadog_agent/install_info.erb'), require => File['/etc/datadog-agent'], } - } } diff --git a/manifests/installer_telemetry.pp b/manifests/installer_telemetry.pp new file mode 100644 index 00000000..3a1850b1 --- /dev/null +++ b/manifests/installer_telemetry.pp @@ -0,0 +1,54 @@ +# This class handles the installation telemetry for the Datadog installer. +# +# @param api_key String:Your DataDog API Key. +# @param datadog_site String: The site of the Datadog intake to send Agent data to. Defaults to 'datadoghq.com'. +# @param packages_to_install String: The packages to be installed by the Datadog installer. +# +class datadog_agent::installer_telemetry ( + String $api_key = 'your_API_key', + String $datadog_site = 'datadoghq.com', + String $packages_to_install = 'datadog-agent', +) { + $role_version = load_module_metadata($module_name)['version'] + + file { 'Trace payload templating': + ensure => file, + path => '/tmp/trace_payload.json', + content => epp('datadog_agent/installer/telemetry/trace.json.epp', { + 'role_version' => $role_version, + 'packages_to_install' => $packages_to_install + } + ), + } + + file { 'Log payload templating': + ensure => file, + path => '/tmp/log_payload.json', + content => epp('datadog_agent/installer/telemetry/log.json.epp', { + 'role_version' => $role_version + } + ), + } + + file { 'Telemetry script templating': + ensure => file, + path => '/tmp/datadog_send_telemetry.sh', + content => epp('datadog_agent/installer/telemetry/send_telemetry.sh.epp', { + 'datadog_site' => $datadog_site, + 'api_key' => $api_key + } + ), + mode => '0744', + require => [ + File['Trace payload templating'], + File['Log payload templating'], + ], + } + + exec { 'Run telemetry script': + # We don't want to fail the installation if telemetry fails and we need to remove the script after running it, hence the semicolon + command => 'bash /tmp/datadog_send_telemetry.sh ; rm -f /tmp/datadog_send_telemetry.sh', + path => ['/usr/bin', '/bin'], + require => File['Telemetry script templating'], + } +} diff --git a/manifests/params.pp b/manifests/params.pp index 26297f4b..8d32da61 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -18,6 +18,7 @@ $process_default_custom_words = [] $logs_enabled = false $logs_open_files_limit = undef + $remote_updates = false $container_collect_all = false $sysprobe_service_name = 'datadog-agent-sysprobe' $securityagent_service_name = 'datadog-agent-security' diff --git a/manifests/redhat_installer.pp b/manifests/redhat_installer.pp new file mode 100644 index 00000000..844c45fb --- /dev/null +++ b/manifests/redhat_installer.pp @@ -0,0 +1,164 @@ +# Class: datadog_agent::redhat_installer +# This class installs and configures the Datadog agent on RedHat-based systems. +# +# @param api_key String:Your DataDog API Key. +# @param datadog_site String: The site of the Datadog intake to send Agent data to. Defaults to 'datadoghq.com'. +# @param agent_major_version Integer: The major version of the Datadog agent to install. Defaults to 7. +# @param agent_minor_version Optional[String]: The minor version of the Datadog agent to install. +# @param installer_repo_uri Optional[String]: The URI of the installer repository. +# @param rpm_repo_gpgcheck Optional[Boolean]: Whether to check the GPG signature of the repository. +# @param apm_instrumentation_enabled Optional[Enum['host', 'docker', 'all']]: Enable APM instrumentation for the specified environment (host, docker, or all). +# @param apm_instrumentation_libraries_str Optional[String]: APM instrumentation libraries as a comma-separated string. +# @param remote_updates Boolean: Whether to enable Agent remote updates. Default: false. +# +class datadog_agent::redhat_installer ( + String $api_key = 'your_API_key', + String $datadog_site = $datadog_agent::params::datadog_site, + Integer $agent_major_version = $datadog_agent::params::default_agent_major_version, + Optional[String] $agent_minor_version = undef, + Optional[String] $installer_repo_uri = undef, + Optional[Boolean] $rpm_repo_gpgcheck = undef, + Optional[Enum['host', 'docker', 'all']] $apm_instrumentation_enabled = undef, + Optional[String] $apm_instrumentation_libraries_str = undef, + Boolean $remote_updates = $datadog_agent::params::remote_updates, +) inherits datadog_agent::params { + # Generate installer trace ID as a random 64-bit integer (Puppet does not support 128-bit integers) + # Note: we cannot use fqdn_rand as the seed is dependent on the node, meaning the same trace ID would be generated on each run (for the same node) + # -An: no address, no leading 0s + # -N8: read 8 bytes + # -tu8: unsigned integer, 8 bytes (64 bits) + exec { 'Generate trace ID': + command => "echo $(od -An -N8 -tu8 < /dev/urandom | tr -d ' ') > /tmp/datadog_trace_id", + path => ['/usr/bin', '/bin'], + onlyif => '/bin/sh -c "command -v tr && command -v od && command -v echo"', + } + + # Start timer (note: Puppet is not able to measure time directly as it's against its paradigm) + exec { 'Start timer': + command => 'date +%s%N > /tmp/puppet_start_time', + path => ['/usr/bin', '/bin'], + require => Exec['Generate trace ID'], + } + + # Define the GPG keys to use for the repository + # We only use the latest key and previous key since the installer is signed with the latest key and the agent might be signed with the previous key. + $all_keys = [ + 'https://keys.datadoghq.com/DATADOG_RPM_KEY_CURRENT.public', + # Previous, EOL September 2024 + 'https://keys.datadoghq.com/DATADOG_RPM_KEY_FD4BF915.public', + # Current + 'https://keys.datadoghq.com/DATADOG_RPM_KEY_B01082D3.public', + # Future, active from April 2028 + 'https://keys.datadoghq.com/DATADOG_RPM_KEY_4F09D16B.public', + ] + + if ($rpm_repo_gpgcheck != undef) { + $repo_gpgcheck = $rpm_repo_gpgcheck + } else { + if $installer_repo_uri == undef { + case $facts['os']['name'] { + 'RedHat', 'CentOS', 'OracleLinux': { + # disable repo_gpgcheck on 8.1 because of https://bugzilla.redhat.com/show_bug.cgi?id=1792506 + if $facts['os']['release']['full'] =~ /^8.1/ { + $repo_gpgcheck = false + } else { + $repo_gpgcheck = true + } + } + default: { + $repo_gpgcheck = true + } + } + } else { + $repo_gpgcheck = false + } + } + + if ($installer_repo_uri != undef) { + $baseurl = $installer_repo_uri + } else { + # Unlike the Agent package, the installer is only within the stable repository, version 7 + # Thus, no differentiation based on Agent major version. + $baseurl = "https://yum.datadoghq.com/stable/7/${facts['os']['architecture']}/" + } + + yumrepo { 'datadog-installer': + enabled => 1, + gpgcheck => 1, + gpgkey => join($all_keys, "\n "), + repo_gpgcheck => $repo_gpgcheck, + descr => 'Datadog, Inc.', + baseurl => $baseurl, + require => Exec['Start timer'], + } + + # Install `datadog-installer` package with latest versions + package { 'datadog-installer': + ensure => 'latest', + require => Yumrepo['datadog-installer'], + } + + # Bootstrap the installer + exec { 'Bootstrap the installer': + # "Hack" to pass the trace ID at runtime instead of compile time + command => '/usr/bin/env DATADOG_TRACE_ID=$(cat /tmp/datadog_trace_id) DATADOG_PARENT_ID=$(cat /tmp/datadog_trace_id) /usr/bin/datadog-bootstrap bootstrap', + environment => [ + "DD_SITE=${datadog_site}", + "DD_API_KEY=${api_key}", + "DD_AGENT_MAJOR_VERSION=${agent_major_version}", + "DD_AGENT_MINOR_VERSION=${agent_minor_version}", + "DD_REMOTE_UPDATES=${remote_updates}", + "DD_APM_INSTRUMENTATION_ENABLED=${apm_instrumentation_enabled}", + "DD_APM_INSTRUMENTATION_LIBRARIES=${apm_instrumentation_libraries_str}", + ], + require => Package['datadog-installer'], + } + + # Check if installer owns the Datadog Agent package + exec { + 'Check if installer owns the Datadog Agent package': + command => '/usr/bin/datadog-installer is-installed datadog-agent', + environment => [ + "DD_SITE=${datadog_site}", + "DD_API_KEY=${api_key}", + ], + # We allow 0, 10 (package not installed) + returns => [0, 10], + require => Exec['Bootstrap the installer'], + } + + # Check if installer owns APM libraries + if $apm_instrumentation_libraries_str != '' { + $apm_instrumentation_libraries_str.split(',').each |$library| { + exec { "Check if installer owns APM library ${library}": + command => "/usr/bin/datadog-installer is-installed datadog-apm-library-${library}", + environment => [ + "DD_SITE=${datadog_site}", + "DD_API_KEY=${api_key}", + ], + # We allow 0, 10 (package not installed) + returns => [0, 10], + require => Exec['Bootstrap the installer'], + } + } + } + + # Stop timer + exec { 'End timer': + command => 'date +%s%N > /tmp/puppet_stop_time', + path => ['/usr/bin', '/bin'], + require => Exec['Bootstrap the installer'], + } + + if $remote_updates { + $packages_to_install = "datadog-agent,${apm_instrumentation_libraries_str}" + } else { + $packages_to_install = $apm_instrumentation_libraries_str + } + class { 'datadog_agent::installer_telemetry': + api_key => $api_key, + datadog_site => $datadog_site, + packages_to_install => $packages_to_install, + require => Exec['End timer'], + } +} diff --git a/manifests/suse_installer.pp b/manifests/suse_installer.pp new file mode 100644 index 00000000..01732f0e --- /dev/null +++ b/manifests/suse_installer.pp @@ -0,0 +1,177 @@ +# Class: datadog_agent::suse_installer +# This class installs and configures the Datadog agent on RedHat-based systems. +# +# @param api_key String:Your DataDog API Key. +# @param datadog_site String: The site of the Datadog intake to send Agent data to. Defaults to 'datadoghq.com'. +# @param agent_major_version Integer: The major version of the Datadog agent to install. Defaults to 7. +# @param agent_minor_version Optional[String]: The minor version of the Datadog agent to install. +# @param installer_repo_uri Optional[String]: The URI of the installer repository. +# @param rpm_repo_gpgcheck Optional[Boolean]: Whether to check the GPG signature of the repository. +# @param apm_instrumentation_enabled Optional[Enum['host', 'docker', 'all']]: Enable APM instrumentation for the specified environment (host, docker, or all). +# @param apm_instrumentation_libraries_str Optional[String]: APM instrumentation libraries as a comma-separated string. +# @param remote_updates Boolean: Whether to enable Agent remote updates. Default: false. +# +class datadog_agent::suse_installer ( + String $api_key = 'your_API_key', + String $datadog_site = $datadog_agent::params::datadog_site, + Integer $agent_major_version = $datadog_agent::params::default_agent_major_version, + Optional[String] $agent_minor_version = undef, + Optional[String] $installer_repo_uri = undef, + Optional[Boolean] $rpm_repo_gpgcheck = undef, + Optional[Enum['host', 'docker', 'all']] $apm_instrumentation_enabled = undef, + Optional[String] $apm_instrumentation_libraries_str = undef, + Boolean $remote_updates = $datadog_agent::params::remote_updates, +) inherits datadog_agent::params { + # Generate installer trace ID as a random 64-bit integer (Puppet does not support 128-bit integers) + # Note: we cannot use fqdn_rand as the seed is dependent on the node, meaning the same trace ID would be generated on each run (for the same node) + # -An: no address, no leading 0s + # -N8: read 8 bytes + # -tu8: unsigned integer, 8 bytes (64 bits) + exec { 'Generate trace ID': + command => "echo $(od -An -N8 -tu8 < /dev/urandom | tr -d ' ') > /tmp/datadog_trace_id", + path => ['/usr/bin', '/bin'], + onlyif => '/bin/sh -c "command -v tr && command -v od && command -v echo"', + } + + # Start timer (note: Puppet is not able to measure time directly as it's against its paradigm) + exec { 'Start timer': + command => 'date +%s%N > /tmp/puppet_start_time', + path => ['/usr/bin', '/bin'], + require => Exec['Generate trace ID'], + } + # Define the GPG keys to use for the repository + # We only use the latest key and previous key since the installer is signed with the latest key and the agent might be signed with the previous key. + $all_keys = [ + 'https://keys.datadoghq.com/DATADOG_RPM_KEY_CURRENT.public', + # Previous, EOL September 2024 + 'https://keys.datadoghq.com/DATADOG_RPM_KEY_FD4BF915.public', + # Current + 'https://keys.datadoghq.com/DATADOG_RPM_KEY_B01082D3.public', + # Future, active from April 2028 + 'https://keys.datadoghq.com/DATADOG_RPM_KEY_4F09D16B.public', + ] + + if ($rpm_repo_gpgcheck != undef) { + $repo_gpgcheck = $rpm_repo_gpgcheck + } else { + if ($installer_repo_uri == undef) { + $repo_gpgcheck = true + } else { + $repo_gpgcheck = false + } + } + + if ($installer_repo_uri != undef) { + $baseurl = $installer_repo_uri + } else { + # Unlike the Agent package, the installer is only within the stable repository, version 7 + # Thus, no differentiation based on Agent major version. + $baseurl = "https://yum.datadoghq.com/suse/stable/7/${facts['os']['architecture']}/" + } + + # We need to install GPG keys manually since zypper will autoreject new keys + # We download each key and import it using rpm --import + $all_keys.each |String $key_url| { + $key_name = split($key_url, '/') + $key_path = "/tmp/${key_name[-1]}" + + file { $key_path: + owner => root, + group => root, + mode => '0600', + source => $key_url, + } + + exec { "install-${key_name}": + command => "/bin/rpm --import ${key_path}", + } + } + + zypprepo { 'datadog-installer': + baseurl => $baseurl, + enabled => 1, + autorefresh => 1, + name => 'datadog-installer', + gpgcheck => 1, + # zypper on SUSE < 15 only understands a single gpgkey value + gpgkey => (Float($facts['os']['release']['full']) >= 15.0) ? { true => join($all_keys, "\n "), default => 'https://keys.datadoghq.com/DATADOG_RPM_KEY_CURRENT.public' }, + # TODO: when updating zypprepo to 4.0.0, uncomment the repo_gpgcheck line + # For now, we can leave this commented, as zypper by default does repodata + # signature checks if the repomd.xml.asc is present, so repodata checks + # are effective for most users anyway. We'll make this explicit when we + # update zypprepo version. + # repo_gpgcheck => $repo_gpgcheck, + keeppackages => 1, + require => Exec['Start timer'], + } + + # Install `datadog-installer` package with latest versions + package { 'datadog-installer': + ensure => 'latest', + require => Zypprepo['datadog-installer'], + } + + # Bootstrap the installer + exec { 'Bootstrap the installer': + # "Hack" to pass the trace ID at runtime instead of compile time + command => '/usr/bin/env DATADOG_TRACE_ID=$(cat /tmp/datadog_trace_id) DATADOG_PARENT_ID=$(cat /tmp/datadog_trace_id) /usr/bin/datadog-bootstrap bootstrap', + environment => [ + "DD_SITE=${datadog_site}", + "DD_API_KEY=${api_key}", + "DD_AGENT_MAJOR_VERSION=${agent_major_version}", + "DD_AGENT_MINOR_VERSION=${agent_minor_version}", + "DD_REMOTE_UPDATES=${remote_updates}", + "DD_APM_INSTRUMENTATION_ENABLED=${apm_instrumentation_enabled}", + "DD_APM_INSTRUMENTATION_LIBRARIES=${apm_instrumentation_libraries_str}", + ], + require => Package['datadog-installer'], + } + + # Check if installer owns the Datadog Agent package + exec { + 'Check if installer owns the Datadog Agent package': + command => '/usr/bin/datadog-installer is-installed datadog-agent', + environment => [ + "DD_SITE=${datadog_site}", + "DD_API_KEY=${api_key}", + ], + # We allow 0, 10 (package not installed) + returns => [0, 10], + require => Exec['Bootstrap the installer'], + } + + # Check if installer owns APM libraries + if $apm_instrumentation_libraries_str != '' { + $apm_instrumentation_libraries_str.split(',').each |$library| { + exec { "Check if installer owns APM library ${library}": + command => "/usr/bin/datadog-installer is-installed datadog-apm-library-${library}", + environment => [ + "DD_SITE=${datadog_site}", + "DD_API_KEY=${api_key}", + ], + # We allow 0, 10 (package not installed) + returns => [0, 10], + require => Exec['Bootstrap the installer'], + } + } + } + + # Stop timer + exec { 'End timer': + command => 'date +%s%N > /tmp/puppet_stop_time', + path => ['/usr/bin', '/bin'], + require => Exec['Bootstrap the installer'], + } + + if $remote_updates { + $packages_to_install = "datadog-agent,${apm_instrumentation_libraries_str}" + } else { + $packages_to_install = $apm_instrumentation_libraries_str + } + class { 'datadog_agent::installer_telemetry': + api_key => $api_key, + datadog_site => $datadog_site, + packages_to_install => $packages_to_install, + require => Exec['End timer'], + } +} diff --git a/manifests/ubuntu_installer.pp b/manifests/ubuntu_installer.pp new file mode 100644 index 00000000..5b83df11 --- /dev/null +++ b/manifests/ubuntu_installer.pp @@ -0,0 +1,190 @@ +# Class: datadog_agent::ubuntu_installer +# This class installs and configures the Datadog agent on Debian distributions. +# +# @param api_key String:Your DataDog API Key. +# @param datadog_site String: The site of the Datadog intake to send Agent data to. Defaults to 'datadoghq.com'. +# @param agent_major_version Integer: The major version of the Datadog agent to install. Defaults to 7. +# @param agent_minor_version Optional[String]: The minor version of the Datadog agent to install. +# @param installer_repo_uri Optional[String]: The URI of the installer repository. +# @param release String: The distribution channel to be used for the APT repo. Eg: 'stable' or 'beta'. Default: stable. +# @param skip_apt_key_trusting Boolean: Skip trusting the apt key. Default is false. +# @param manage_agent_install Boolean: Whether Puppet should manage the regular Agent installation. Default is true (inherited from $manage_install). +# @param apt_trusted_d_keyring String: The path to the trusted keyring file. +# @param apt_usr_share_keyring String: The path to the keyring file in /usr/share. +# @param apt_default_keys Hash[String, String]: A hash of default APT keys and their URLs. +# @param apm_instrumentation_enabled Optional[Enum['host', 'docker', 'all']]: Enable APM instrumentation for the specified environment (host, docker, or all). +# @param apm_instrumentation_libraries_str Optional[String]: APM instrumentation libraries as a comma-separated string. +# @param remote_updates Boolean: Whether to enable Agent remote updates. Default: false. +# +class datadog_agent::ubuntu_installer ( + String $api_key = 'your_API_key', + String $datadog_site = $datadog_agent::params::datadog_site, + Integer $agent_major_version = $datadog_agent::params::default_agent_major_version, + Optional[String] $agent_minor_version = undef, + Boolean $manage_agent_install = true, + Optional[String] $installer_repo_uri = undef, + String $release = $datadog_agent::params::apt_default_release, + Boolean $skip_apt_key_trusting = false, + String $apt_trusted_d_keyring = '/etc/apt/trusted.gpg.d/datadog-archive-keyring.gpg', + String $apt_usr_share_keyring = '/usr/share/keyrings/datadog-archive-keyring.gpg', + Hash[String, String] $apt_default_keys = { + # DATADOG_APT_KEY_CURRENT.public always contains key used to sign current + # repodata and newly released packages. + 'DATADOG_APT_KEY_CURRENT.public' => 'https://keys.datadoghq.com/DATADOG_APT_KEY_CURRENT.public', + # DATADOG_APT_KEY_06462314.public expires in 2033 + 'D18886567EABAD8B2D2526900D826EB906462314' => 'https://keys.datadoghq.com/DATADOG_APT_KEY_06462314.public', + # DATADOG_APT_KEY_C0962C7D.public expires in 2028 + '5F1E256061D813B125E156E8E6266D4AC0962C7D' => 'https://keys.datadoghq.com/DATADOG_APT_KEY_C0962C7D.public', + # DATADOG_APT_KEY_F14F620E.public expires in 2032 + 'D75CEA17048B9ACBF186794B32637D44F14F620E' => 'https://keys.datadoghq.com/DATADOG_APT_KEY_F14F620E.public', + # DATADOG_APT_KEY_382E94DE.public expires in 2022 + 'A2923DFF56EDA6E76E55E492D3A80E30382E94DE' => 'https://keys.datadoghq.com/DATADOG_APT_KEY_382E94DE.public', + }, + Optional[Enum['host', 'docker', 'all']] $apm_instrumentation_enabled = undef, + Optional[String] $apm_instrumentation_libraries_str = undef, + Boolean $remote_updates = $datadog_agent::params::remote_updates, +) inherits datadog_agent::params { + # Generate installer trace ID as a random 64-bit integer (Puppet does not support 128-bit integers) + # Note: we cannot use fqdn_rand as the seed is dependent on the node, meaning the same trace ID would be generated on each run (for the same node) + # -An: no address, no leading 0s + # -N8: read 8 bytes + # -tu8: unsigned integer, 8 bytes (64 bits) + exec { 'Generate trace ID': + command => "echo $(od -An -N8 -tu8 < /dev/urandom | tr -d ' ') > /tmp/datadog_trace_id", + path => ['/usr/bin', '/bin'], + onlyif => '/bin/sh -c "command -v tr && command -v od && command -v echo"', + } + + # Start timer (note: Puppet is not able to measure time directly as it's against its paradigm) + exec { 'Start timer': + command => 'date +%s%N > /tmp/puppet_start_time', + path => ['/usr/bin', '/bin'], + require => Exec['Generate trace ID'], + } + + # Do not re-install keys as it is already managed in `ubuntu.pp` + if ! $manage_agent_install { + if !$skip_apt_key_trusting { + ensure_packages(['gnupg']) + + file { $apt_usr_share_keyring: + ensure => file, + mode => '0644', + require => Exec['Start timer'], + } + + $apt_default_keys.each |String $key_fingerprint, String $key_url| { + $key_path = "/tmp/${key_fingerprint}" + + file { $key_path: + owner => root, + group => root, + mode => '0600', + source => $key_url, + } + + exec { "ensure key ${key_fingerprint} is imported in APT keyring": + command => "/bin/cat /tmp/${key_fingerprint} | gpg --import --batch --no-default-keyring --keyring ${apt_usr_share_keyring}", + # the second part extracts the fingerprint of the key from output like "fpr::::A2923DFF56EDA6E76E55E492D3A80E30382E94DE:" + unless => @("CMD"/L) + /usr/bin/gpg --no-default-keyring --keyring ${apt_usr_share_keyring} --list-keys --with-fingerprint --with-colons | grep \ + $(cat /tmp/${key_fingerprint} | gpg --with-colons --with-fingerprint 2>/dev/null | grep 'fpr:' | sed 's|^fpr||' | tr -d ':') + | CMD + } + } + if ($facts['os']['name'] == 'Ubuntu' and versioncmp($facts['os']['release']['full'], '16') == -1) or + ($facts['os']['name'] == 'Debian' and versioncmp($facts['os']['release']['full'], '9') == -1) { + file { $apt_trusted_d_keyring: + mode => '0644', + source => "file://${apt_usr_share_keyring}", + } + } + } + } + + if ($installer_repo_uri != undef) { + $location = $installer_repo_uri + } else { + $location = "[signed-by=${apt_usr_share_keyring}] https://apt.datadoghq.com/" + } + + # Install APT repository + apt::source { 'datadog-installer': + # Installer is located in the same APT repository as the Agent, only within repo 7 + comment => 'Datadog Installer Repository', + location => $location, + release => $release, + repos => '7', + require => Exec['Start timer'], + } + + # Install `datadog-installer` package with latest versions + package { 'datadog-installer': + ensure => 'latest', + require => [Apt::Source['datadog-installer'], Class['apt::update']], + } + + # Bootstrap the installer + exec { 'Bootstrap the installer': + # "Hack" to pass the trace ID at runtime instead of compile time + command => '/usr/bin/env DATADOG_TRACE_ID=$(cat /tmp/datadog_trace_id) DATADOG_PARENT_ID=$(cat /tmp/datadog_trace_id) /usr/bin/datadog-bootstrap bootstrap', + environment => [ + "DD_SITE=${datadog_site}", + "DD_API_KEY=${api_key}", + "DD_AGENT_MAJOR_VERSION=${agent_major_version}", + "DD_AGENT_MINOR_VERSION=${agent_minor_version}", + "DD_REMOTE_UPDATES=${remote_updates}", + "DD_APM_INSTRUMENTATION_ENABLED=${apm_instrumentation_enabled}", + "DD_APM_INSTRUMENTATION_LIBRARIES=${apm_instrumentation_libraries_str}", + ], + require => Package['datadog-installer'], + } + + # Check if installer owns the Datadog Agent package + exec { + 'Check if installer owns the Datadog Agent package': + command => '/usr/bin/datadog-installer is-installed datadog-agent', + environment => [ + "DD_SITE=${datadog_site}", + "DD_API_KEY=${api_key}", + ], + # We allow 0, 10 (package not installed) + returns => [0, 10], + require => Exec['Bootstrap the installer'], + } + + # Check if installer owns APM libraries + if $apm_instrumentation_libraries_str != '' { + $apm_instrumentation_libraries_str.split(',').each |$library| { + exec { "Check if installer owns APM library ${library}": + command => "/usr/bin/datadog-installer is-installed datadog-apm-library-${library}", + environment => [ + "DD_SITE=${datadog_site}", + "DD_API_KEY=${api_key}", + ], + # We allow 0, 10 (package not installed) + returns => [0, 10], + require => Exec['Bootstrap the installer'], + } + } + } + + # Stop timer + exec { 'End timer': + command => 'date +%s%N > /tmp/puppet_stop_time', + path => ['/usr/bin', '/bin'], + require => Exec['Bootstrap the installer'], + } + + if $remote_updates { + $packages_to_install = "datadog-agent,${apm_instrumentation_libraries_str}" + } else { + $packages_to_install = $apm_instrumentation_libraries_str + } + class { 'datadog_agent::installer_telemetry': + api_key => $api_key, + datadog_site => $datadog_site, + packages_to_install => $packages_to_install, + require => Exec['End timer'], + } +} diff --git a/templates/installer/telemetry/log.json.epp b/templates/installer/telemetry/log.json.epp new file mode 100644 index 00000000..c0dd58f8 --- /dev/null +++ b/templates/installer/telemetry/log.json.epp @@ -0,0 +1,39 @@ +{ + "api_version": "v2", + "request_type": "logs", + "tracer_time": TRACER_END_TIME_PLACEHOLDER, + "runtime_id": "RUNTIME_ID_PLACEHOLDER", + "seq_id": 2, + "origin": "puppet", + "host": { + "hostname": "<%= $hostname %>", + "os": "<%= $facts['os']['name'] %>", + "distribution": "<%= $facts['os']['family'] %>", + "architecture": "<%= $facts['os']['architecture'] %>", + "kernel_version": "<%= $facts['kernelversion'] %>", + "kernel_release": "<%= $facts['kernelrelease'] %>" + }, + "application": { + "service_name": "datadog-puppet", + "service_version": "<%= $role_version %>", + "language_name": "UNKNOWN", + "language_version": "n/a", + "tracer_version": "n/a" + }, + "payload": { + "logs": [ + { + "message": "", + "level": "DEBUG", + "trace_id": "RUNTIME_ID_PLACEHOLDER", + "span_id": "RUNTIME_ID_PLACEHOLDER" + }, + { + "message": "", + "level": "ERROR", + "trace_id": "RUNTIME_ID_PLACEHOLDER", + "span_id": "RUNTIME_ID_PLACEHOLDER" + } + ] + } +} diff --git a/templates/installer/telemetry/send_telemetry.sh.epp b/templates/installer/telemetry/send_telemetry.sh.epp new file mode 100644 index 00000000..d628670e --- /dev/null +++ b/templates/installer/telemetry/send_telemetry.sh.epp @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +set -e + +# Telemetry URL based on user-provided site +TELEMETRY_URL="https://instrumentation-telemetry-intake.<%= $datadog_site %>/api/v2/apmtelemetry" + +# Retrieve runtime parameters +TRACER_START_TIME=$(' \ + -d "@$file" \ + "$TELEMETRY_URL" +} + +# Send trace and log payloads +send_payload /tmp/trace_payload.json +send_payload /tmp/log_payload.json diff --git a/templates/installer/telemetry/trace.json.epp b/templates/installer/telemetry/trace.json.epp new file mode 100644 index 00000000..bfb274a7 --- /dev/null +++ b/templates/installer/telemetry/trace.json.epp @@ -0,0 +1,52 @@ +{ + "api_version": "v2", + "request_type": "traces", + "tracer_time": TRACER_END_TIME_PLACEHOLDER, + "runtime_id": "RUNTIME_ID_PLACEHOLDER", + "seq_id": 1, + "origin": "puppet", + "host": { + "hostname": "<%= $hostname %>", + "os": "<%= $facts['os']['name'] %>", + "distribution": "<%= $facts['os']['family'] %>", + "architecture": "<%= $facts['os']['architecture'] %>", + "kernel_version": "<%= $facts['kernelversion'] %>", + "kernel_release": "<%= $facts['kernelrelease'] %>" + }, + "application": { + "service_name": "datadog-puppet", + "service_version": "<%= $role_version %>", + "language_name": "UNKNOWN", + "language_version": "n/a", + "tracer_version": "n/a" + }, + "payload": { + "traces": [ + [ + { + "service": "datadog-puppet", + "name": "install_installer", + "resource": "install_installer", + "trace_id": RUNTIME_ID_PLACEHOLDER, + "span_id": RUNTIME_ID_PLACEHOLDER, + "parent_id": 0, + "start": TRACER_START_TIME_PLACEHOLDER, + "duration": DURATION_PLACEHOLDER, + "error": 0, + "meta": { + "language": "puppet", + "exit_code": 0, + "version": "<%= $role_version %>", + "packages_to_install": "<%= $packages_to_install %>" + }, + "metrics": { + "_trace_root": 1, + "_top_level": 1, + "_dd.top_level": 1, + "_sampling_priority_v1": 2 + } + } + ] + ] + } +} diff --git a/test/integration/dd-installer/serverspec/default_spec.rb b/test/integration/dd-installer/serverspec/default_spec.rb new file mode 100644 index 00000000..8f8d316d --- /dev/null +++ b/test/integration/dd-installer/serverspec/default_spec.rb @@ -0,0 +1,21 @@ +require_relative 'spec_helper' + +describe 'datadog_installer' do + describe file('/opt/datadog-packages') do + it { is_expected.to be_directory } + end + + describe package('datadog-installer') do + it { is_expected.to be_installed } + end + + describe command('/usr/bin/datadog-installer is-installed datadog-apm-library-java') do + # Installed, exit code should be 0 + its(:exit_status) { is_expected.to eq 0 } + end + + describe command('/usr/bin/datadog-installer is-installed datadog-apm-library-dotnet') do + # Not installed, exit code should be 10 + its(:exit_status) { is_expected.to eq 10 } + end +end diff --git a/test/integration/dd-installer/serverspec/spec_helper.rb b/test/integration/dd-installer/serverspec/spec_helper.rb new file mode 100644 index 00000000..0419e0ae --- /dev/null +++ b/test/integration/dd-installer/serverspec/spec_helper.rb @@ -0,0 +1,5 @@ +require 'serverspec' + +# # :backend can be either :exec or :ssh +# # since we are running local we use :exec +set :backend, :exec