diff --git a/recipes/newrelic/infrastructure/nrdot/mssql-otel/windows-rds.yml b/recipes/newrelic/infrastructure/nrdot/mssql-otel/windows-rds.yml new file mode 100644 index 000000000..6874652d8 --- /dev/null +++ b/recipes/newrelic/infrastructure/nrdot/mssql-otel/windows-rds.yml @@ -0,0 +1,1083 @@ +# Visit our schema definition for additional information on this file format. +# https://github.com/newrelic/open-install-library/blob/main/docs/recipe-spec/recipe-spec.md#schema-definition + +name: nrdot-collector-mssql-rds +displayName: NRDOT MSSQL RDS (Windows) +description: New Relic install recipe for MSSQL monitoring via NRDOT Collector on Windows connecting to AWS RDS SQL Server instances +repository: https://github.com/newrelic/nrdot-collector-releases + +installTargets: + - type: host + os: windows + +keywords: + - MSSQL + - SQL Server + - NRDOT + - OpenTelemetry + - OTel + - Database + - Windows + - RDS + - AWS + +processMatch: [] + +preInstall: + discoveryMode: + - targeted + info: | + To capture data from your AWS RDS SQL Server instance, we need to create/authorize + a monitoring identity (a SQL login, a Windows domain account, or a gMSA account + depending on the authentication method you choose). For SQL Server Auth this + requires your RDS master user (or a user with the rds_superuser role); for Windows + Domain Auth / gMSA this requires administrative access to your RDS instance. This + host also needs network connectivity to the RDS endpoint (inbound access on the + SQL Server port in the RDS security group). + +inputVars: + - name: NR_CLI_MSSQL_CONFIG_PRESET + prompt: "NRDOT configuration - 1) Standard 2) Full-feature: " + default: "1" + - name: NR_CLI_MSSQL_AUTH_MODE + prompt: "SQL Server authentication - 1) SQL Server Auth 2) Windows Domain Auth 3) gMSA: " + default: "1" + - name: NR_CLI_MSSQL_SERVER + prompt: "RDS SQL Server endpoint (e.g. mydb.xxxxxxxxxx.us-east-1.rds.amazonaws.com): " + - name: NR_CLI_MSSQL_PORT + prompt: "SQL Server port: " + default: "1433" + - name: NR_CLI_MSSQL_MASTER_USER + prompt: "RDS master username (mode 1 only; leave blank otherwise): " + default: " " + - name: NR_CLI_MSSQL_MASTER_PASSWORD + prompt: "RDS master user password (mode 1 only, used once to create the monitoring login; leave blank otherwise): " + secret: true + default: " " + - name: NR_CLI_MSSQL_LOGIN_NAME + prompt: "Monitoring username: " + default: "newrelic" + - name: NR_CLI_MSSQL_WIN_ACCOUNT + prompt: "Windows domain account to grant permissions to, DOMAIN\\username (mode 2 only; leave blank otherwise): " + default: " " + - name: NR_CLI_MSSQL_WIN_PASSWORD + prompt: "Password for that domain account (mode 2 only; leave blank otherwise): " + secret: true + default: " " + - name: NR_CLI_MSSQL_GMSA_ACCOUNT + prompt: "gMSA account, DOMAIN\\gMSAName$ (mode 3 only; leave blank otherwise): " + default: " " + +validationNrql: "SELECT count(*) FROM Metric WHERE metricName LIKE 'sqlserver.%' AND instrumentation.provider = 'opentelemetry' SINCE 10 minutes ago" + +successLinkConfig: + type: EXPLORER + +install: + version: "3" + silent: true + + tasks: + write_recipe_metadata: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $metadata = '{"Metadata":{"CapturedCliOutput":"true"}}' + try { $metadata | Set-Content {{.NR_CLI_OUTPUT}} } catch {} + PSEOF + + assert_pre_req: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) + $isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + if (-not $isAdmin) { + Write-Host -ForegroundColor Red "This newrelic install must be run in an Administrator PowerShell session." + exit 131 + } + + $sqlcmdPath = Get-Command sqlcmd.exe -ErrorAction SilentlyContinue + if (-not $sqlcmdPath) { + Write-Host -ForegroundColor Red "sqlcmd is required to configure SQL Server monitoring. Install the sqlcmd utility (https://learn.microsoft.com/sql/tools/sqlcmd/sqlcmd-utility) and re-run this recipe." + exit 16 + } + PSEOF + + assert_auth_inputs: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + + switch ($mode) { + "1" { + $masterUser = '{{.NR_CLI_MSSQL_MASTER_USER}}' + $masterPassword = '{{.NR_CLI_MSSQL_MASTER_PASSWORD}}' + if ([string]::IsNullOrWhiteSpace($masterUser) -or $masterUser.Trim() -eq "" -or [string]::IsNullOrWhiteSpace($masterPassword) -or $masterPassword.Trim() -eq "") { + Write-Host -ForegroundColor Red "Error: RDS master username and password are both required for SQL Server Auth (mode 1)." + Write-Host -ForegroundColor Red "Please re-run the installation and provide valid RDS master credentials." + exit 1 + } + } + "2" { + $winAccount = '{{.NR_CLI_MSSQL_WIN_ACCOUNT}}' + $winPassword = '{{.NR_CLI_MSSQL_WIN_PASSWORD}}' + if ([string]::IsNullOrWhiteSpace($winAccount) -or $winAccount.Trim() -eq "" -or [string]::IsNullOrWhiteSpace($winPassword) -or $winPassword.Trim() -eq "") { + Write-Host -ForegroundColor Red "Error: Windows domain account and password are both required for Windows Domain Auth (mode 2)." + Write-Host -ForegroundColor Red "Please re-run the installation and provide valid credentials." + exit 1 + } + } + "3" { + $gmsaAccount = '{{.NR_CLI_MSSQL_GMSA_ACCOUNT}}' + if ([string]::IsNullOrWhiteSpace($gmsaAccount) -or $gmsaAccount.Trim() -eq "") { + Write-Host -ForegroundColor Red "Error: gMSA account is required for gMSA Auth (mode 3)." + Write-Host -ForegroundColor Red "Please re-run the installation and provide a valid gMSA account." + exit 1 + } + } + default { + Write-Host -ForegroundColor Red "Error: NR_CLI_MSSQL_AUTH_MODE must be 1, 2, or 3." + exit 1 + } + } + PSEOF + + assert_sql_server_version: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + $server = '{{.NR_CLI_MSSQL_SERVER}}' + $port = '{{.NR_CLI_MSSQL_PORT}}' + + if ($mode -eq "1") { + $masterUser = '{{.NR_CLI_MSSQL_MASTER_USER}}' + $masterPassword = '{{.NR_CLI_MSSQL_MASTER_PASSWORD}}' + if ([string]::IsNullOrEmpty($masterPassword)) { + Write-Host -ForegroundColor Red "RDS master password is empty when preparing to connect to SQL Server - this should not happen." + exit 1 + } + $env:SQLCMDPASSWORD = $masterPassword + $connArgs = @("-S", "$server,$port", "-U", $masterUser, "-C") + } else { + $connArgs = @("-S", "$server,$port", "-E", "-C") + } + + $query = "SET NOCOUNT ON; SELECT SERVERPROPERTY('ProductMajorVersion');" + $result = & sqlcmd @connArgs -b -h -1 -Q $query 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to connect to SQL Server to check its version:" + Write-Host $result + exit 1 + } + + $majorVersionLine = ($result | Where-Object { $_ -match '^\s*\d+\s*$' } | Select-Object -First 1) + $majorVersion = if ($majorVersionLine) { [int]$majorVersionLine.Trim() } else { $null } + + if (-not $majorVersion -or $majorVersion -lt 14) { + Write-Host -ForegroundColor Red "SQL Server version $majorVersion is not supported. SQL Server 2017 or later (major version 14+) is required." + exit 1 + } + + Write-Host "SQL Server major version $majorVersion detected - supported." + PSEOF + + install_nrdot: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + [Net.ServicePointManager]::SecurityProtocol = "tls12, tls" + + $CollectorService = "nrdot-collector" + + $LatestVersion = (Invoke-RestMethod -Uri "https://api.github.com/repos/newrelic/nrdot-collector-releases/releases/latest").tag_name + if (-not $LatestVersion) { + Write-Host -ForegroundColor Red "Failed to fetch the latest release version from GitHub. Please check your internet connection." + exit 1 + } + + $ExistingService = Get-Service -Name $CollectorService -ErrorAction SilentlyContinue + + if ($ExistingService) { + Write-Host -ForegroundColor Yellow "NRDOT Collector is already installed." + Write-Host "" + Write-Host "Please follow the steps below to complete the setup:" + Write-Host "" + Write-Host " 1. Create mssql-config.yaml" + Write-Host " if not present. Refer to:" + Write-Host " https://docs.newrelic.com/docs/opentelemetry/database/mssql/windows-rds/" + Write-Host "" + Write-Host " 2. Make sure the monitoring user is created" + Write-Host " and has the required permissions granted." + Write-Host "" + Write-Host " 3. After making the above changes, restart the NRDOT Collector:" + Write-Host " net stop nrdot-collector; net start nrdot-collector" + Write-Host "" + exit 131 + } + + Write-Host "Installing nrdot-collector version: $LatestVersion" + + $MsiPath = "$env:TEMP\nrdot-collector.msi" + $LogPath = "$env:TEMP\nrdot_install.log" + $DownloadUrl = "https://github.com/newrelic/nrdot-collector-releases/releases/download/$LatestVersion/nrdot-collector_${LatestVersion}_windows_x64.msi" + Write-Host "Downloading from: $DownloadUrl" + + $WebClient = New-Object System.Net.WebClient + $WebClient.Headers.Add("User-Agent", "Mozilla/5.0") + if ($env:HTTPS_PROXY) { + $WebClient.Proxy = New-Object System.Net.WebProxy($env:HTTPS_PROXY, $true) + } + try { + $WebClient.DownloadFile($DownloadUrl, $MsiPath) + } catch { + Write-Host -ForegroundColor Red "Failed to download the nrdot-collector package: $_" + exit 1 + } + + $Process = Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$MsiPath`" /qn /norestart /L*V `"$LogPath`"" -Wait -NoNewWindow -PassThru + if ($Process.ExitCode -ne 0) { + Write-Host -ForegroundColor Red "msiexec failed with exit code $($Process.ExitCode). See $LogPath for details." + exit $Process.ExitCode + } + + Remove-Item -Path $MsiPath -ErrorAction SilentlyContinue + + $Service = Get-Service -Name $CollectorService -ErrorAction SilentlyContinue + if (-not $Service) { + Write-Host -ForegroundColor Red "nrdot-collector service was not found after installation." + exit 1 + } + + Write-Host "nrdot-collector installed successfully." + PSEOF + + configure_database_user: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + $server = '{{.NR_CLI_MSSQL_SERVER}}' + $port = '{{.NR_CLI_MSSQL_PORT}}' + $sqlFile = "$env:TEMP\nr-mssql-grant.sql" + + function Invoke-GrantScript($connArgs, $file) { + $result = & sqlcmd @connArgs -b -i $file 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "SQL script failed:" + Write-Host $result + exit 1 + } + Write-Host $result + } + + function Protect-TempFile($path) { + icacls $path /inheritance:r /grant:r "SYSTEM:(F)" "BUILTIN\Administrators:(F)" | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to restrict permissions on $path." + exit 1 + } + } + + try { + if ($mode -eq "1") { + $loginName = '{{.NR_CLI_MSSQL_LOGIN_NAME}}' + $masterUser = '{{.NR_CLI_MSSQL_MASTER_USER}}' + $chars = (48..57) + (65..90) + (97..122) + $NrPassword = -join ($chars | Get-Random -Count 24 | ForEach-Object {[char]$_}) + + $sql = @" + USE [master]; + GO + IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = '$loginName') + CREATE LOGIN [$loginName] WITH PASSWORD = '$NrPassword'; + ELSE + ALTER LOGIN [$loginName] WITH PASSWORD = '$NrPassword'; + GO + GRANT VIEW SERVER STATE TO [$loginName]; + GRANT VIEW ANY DEFINITION TO [$loginName]; + GRANT VIEW ANY DATABASE TO [$loginName]; + GO + DECLARE @name SYSNAME; + DECLARE db_cursor CURSOR READ_ONLY FORWARD_ONLY FOR + SELECT [name] + FROM [master].[sys].[databases] + WHERE [name] NOT IN ('master', 'msdb', 'model', 'rdsadmin', 'distribution') + AND [state] = 0; + OPEN db_cursor; + FETCH NEXT FROM db_cursor INTO @name; + WHILE @@FETCH_STATUS = 0 + BEGIN + BEGIN TRY + EXEC('USE [' + @name + ']; + IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = ''$loginName'') + BEGIN + CREATE USER [$loginName] FOR LOGIN [$loginName]; + END; + GRANT VIEW DATABASE STATE TO [$loginName];'); + END TRY + BEGIN CATCH + PRINT 'Error on ' + @name + ': ' + ERROR_MESSAGE(); + END CATCH + FETCH NEXT FROM db_cursor INTO @name; + END + CLOSE db_cursor; + DEALLOCATE db_cursor; + GO + "@ + $sql | Set-Content -Path $sqlFile + Protect-TempFile $sqlFile + $masterPassword = '{{.NR_CLI_MSSQL_MASTER_PASSWORD}}' + if ([string]::IsNullOrEmpty($masterPassword)) { + Write-Host -ForegroundColor Red "RDS master password is empty when preparing to connect to SQL Server - this should not happen." + exit 1 + } + $env:SQLCMDPASSWORD = $masterPassword + Invoke-GrantScript @("-S", "$server,$port", "-U", $masterUser, "-C") $sqlFile + } + elseif ($mode -eq "2") { + $account = '{{.NR_CLI_MSSQL_WIN_ACCOUNT}}' + $sql = @" + USE [master]; + GO + IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = '$account') + CREATE LOGIN [$account] FROM WINDOWS; + GO + GRANT VIEW SERVER STATE TO [$account]; + GRANT VIEW ANY DEFINITION TO [$account]; + GRANT VIEW ANY DATABASE TO [$account]; + GO + DECLARE @name SYSNAME; + DECLARE db_cursor CURSOR READ_ONLY FORWARD_ONLY FOR + SELECT [name] + FROM [master].[sys].[databases] + WHERE [name] NOT IN ('master', 'msdb', 'model', 'rdsadmin', 'distribution') + AND [state] = 0; + OPEN db_cursor; + FETCH NEXT FROM db_cursor INTO @name; + WHILE @@FETCH_STATUS = 0 + BEGIN + BEGIN TRY + EXEC('USE [' + @name + ']; + IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = ''$account'') + BEGIN + CREATE USER [$account] FOR LOGIN [$account]; + END; + GRANT VIEW DATABASE STATE TO [$account];'); + END TRY + BEGIN CATCH + PRINT 'Error on ' + @name + ': ' + ERROR_MESSAGE(); + END CATCH + FETCH NEXT FROM db_cursor INTO @name; + END + CLOSE db_cursor; + DEALLOCATE db_cursor; + GO + "@ + $sql | Set-Content -Path $sqlFile + Protect-TempFile $sqlFile + Invoke-GrantScript @("-S", "$server,$port", "-E", "-C") $sqlFile + } + else { + $gmsa = '{{.NR_CLI_MSSQL_GMSA_ACCOUNT}}' + $sql = @" + USE master; + GO + IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = '$gmsa') + CREATE LOGIN [$gmsa] FROM WINDOWS; + GO + GRANT VIEW SERVER STATE TO [$gmsa]; + GRANT VIEW ANY DEFINITION TO [$gmsa]; + GRANT VIEW ANY DATABASE TO [$gmsa]; + GO + DECLARE @name SYSNAME; + DECLARE db_cursor CURSOR READ_ONLY FORWARD_ONLY FOR + SELECT [name] FROM [master].[sys].[databases] + WHERE [name] NOT IN ('master','msdb','model','rdsadmin','distribution') + AND [state] = 0; + OPEN db_cursor; + FETCH NEXT FROM db_cursor INTO @name; + WHILE @@FETCH_STATUS = 0 + BEGIN + BEGIN TRY + EXEC('USE [' + @name + ']; + IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = ''$gmsa'') + BEGIN + CREATE USER [$gmsa] FOR LOGIN [$gmsa]; + END; + GRANT VIEW DATABASE STATE TO [$gmsa];'); + END TRY + BEGIN CATCH + PRINT 'Error on ' + @name + ': ' + ERROR_MESSAGE(); + END CATCH + FETCH NEXT FROM db_cursor INTO @name; + END + CLOSE db_cursor; + DEALLOCATE db_cursor; + GO + "@ + $sql | Set-Content -Path $sqlFile + Protect-TempFile $sqlFile + Invoke-GrantScript @("-S", "$server,$port", "-E", "-C") $sqlFile + } + + Write-Host "SQL Server monitoring identity configured successfully." + } + finally { + Remove-Item -Path $sqlFile -ErrorAction SilentlyContinue + } + PSEOF + + configure_service_identity: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + $service = "nrdot-collector" + + if ($mode -eq "1") { + Write-Host "SQL Server Auth selected - service logon account left unchanged (LocalSystem)." + exit 0 + } + + if ($mode -eq "2") { + $account = '{{.NR_CLI_MSSQL_WIN_ACCOUNT}}' + $password = '{{.NR_CLI_MSSQL_WIN_PASSWORD}}' + } else { + $account = '{{.NR_CLI_MSSQL_GMSA_ACCOUNT}}' + $password = "" + } + + Stop-Service -Name $service -Force -ErrorAction SilentlyContinue + + & sc.exe config "$service" obj= "$account" password= "$password" + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to configure the nrdot-collector service to run as $account." + exit 1 + } + + $svcInfo = Get-WmiObject Win32_Service -Filter "Name='$service'" + Write-Host "nrdot-collector service logon account set to: $($svcInfo.StartName)" + + Start-Service -Name $service + PSEOF + + create_collector_config: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + $preset = "{{.NR_CLI_MSSQL_CONFIG_PRESET}}" + $server = '{{.NR_CLI_MSSQL_SERVER}}' + $port = '{{.NR_CLI_MSSQL_PORT}}' + $region = "{{.NEW_RELIC_REGION}}" + $licenseKey = "{{.NEW_RELIC_LICENSE_KEY}}" + $configDir = "C:\Program Files\nrdot-collector" + $configPath = "$configDir\mssql-config.yaml" + + New-Item -Path $configDir -ItemType Directory -Force | Out-Null + + $interval = if ($preset -eq "2") { "30s" } else { "15s" } + + if ($mode -eq "1") { + $loginName = '{{.NR_CLI_MSSQL_LOGIN_NAME}}' + $masterUser = '{{.NR_CLI_MSSQL_MASTER_USER}}' + $chars = (48..57) + (65..90) + (97..122) + $NrPassword = -join ($chars | Get-Random -Count 24 | ForEach-Object {[char]$_}) + $env:SQLCMDPASSWORD = '{{.NR_CLI_MSSQL_MASTER_PASSWORD}}' + $alterResult = & sqlcmd -S "$server,$port" -U $masterUser -C -b -Q "ALTER LOGIN [$loginName] WITH PASSWORD = '$NrPassword';" 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to set the monitoring login's password:" + Write-Host $alterResult + exit 1 + } + $receiverFields = " collection_interval: $interval`n username: $loginName`n password: $NrPassword`n server: $server`n port: $port" + } else { + $receiverFields = " collection_interval: $interval`n datasource: `"server=$server;port=$port;integrated security=true;encrypt=true;TrustServerCertificate=true;`"" + } + + switch ($region) { + "staging" { $otlpEndpoint = "https://staging-otlp.nr-data.net" } + "EU" { $otlpEndpoint = "https://otlp.eu01.nr-data.net" } + "JP" { $otlpEndpoint = "https://otlp.jp.nr-data.net" } + default { $otlpEndpoint = "https://otlp.nr-data.net" } + } + + if ($preset -eq "2") { + $template = @' + extensions: + health_check: + + receivers: + otlp: + protocols: + grpc: + http: + + host_metrics: + collection_interval: 30s + scrapers: + cpu: + metrics: + system.cpu.time: + enabled: false + system.cpu.utilization: + enabled: true + load: + memory: + metrics: + system.memory.utilization: + enabled: true + paging: + metrics: + system.paging.utilization: + enabled: false + system.paging.faults: + enabled: false + disk: + metrics: + system.disk.merged: + enabled: false + system.disk.pending_operations: + enabled: false + system.disk.weighted_io_time: + enabled: false + network: + metrics: + system.network.connections: + enabled: false + processes: + process: + mute_process_name_error: true + mute_process_exe_error: true + mute_process_user_error: true + mute_process_io_error: true + metrics: + process.cpu.utilization: + enabled: true + process.memory.utilization: + enabled: true + + nrsqlserver: + __RECEIVER_FIELDS__ + metrics: + sqlserver.database.count: + enabled: true + sqlserver.database.file.size: + enabled: true + sqlserver.database.io: + enabled: true + sqlserver.database.latency: + enabled: true + sqlserver.database.operations: + enabled: true + sqlserver.database.transactions.active: + enabled: true + sqlserver.database.backup_or_restore.rate: + enabled: true + sqlserver.database.full_scan.rate: + enabled: true + sqlserver.database.tempdb.space: + enabled: true + sqlserver.database.tempdb.version_store.size: + enabled: true + sqlserver.lock.timeout.rate: + enabled: true + sqlserver.lock.wait.count: + enabled: true + sqlserver.deadlock.rate: + enabled: true + sqlserver.transaction.delay: + enabled: true + sqlserver.transaction.longest_running_time: + enabled: true + sqlserver.transaction.version_cleanup.rate: + enabled: true + sqlserver.transaction.version_generation.rate: + enabled: true + sqlserver.memory.area: + enabled: true + sqlserver.memory.cache.object.count: + enabled: true + sqlserver.memory.grants.pending.count: + enabled: true + sqlserver.memory.page.count: + enabled: true + sqlserver.memory.usage: + enabled: true + sqlserver.os.memory.usage: + enabled: true + sqlserver.os.memory.utilization: + enabled: true + sqlserver.os.scheduler.runnable_tasks.count: + enabled: true + sqlserver.os.wait.duration: + enabled: true + sqlserver.os.wait.tasks.count: + enabled: true + sqlserver.page.buffer_cache.free_list.stalls.rate: + enabled: true + sqlserver.page.lookup.rate: + enabled: true + sqlserver.batch.compilation.utilization: + enabled: true + sqlserver.batch.page_split.utilization: + enabled: true + sqlserver.recompilation.ratio: + enabled: true + sqlserver.parameterization.rate: + enabled: true + sqlserver.plan.execution.rate: + enabled: true + sqlserver.attention.rate: + enabled: true + sqlserver.process.count: + enabled: true + sqlserver.processes.blocked: + enabled: true + sqlserver.login.rate: + enabled: true + sqlserver.logout.rate: + enabled: true + sqlserver.thread_pool.tasks.count: + enabled: true + sqlserver.thread_pool.workers.count: + enabled: true + sqlserver.thread_pool.workers.max: + enabled: true + sqlserver.thread_pool.workers.utilization: + enabled: true + sqlserver.tempdb.allocation.wait_time.total: + enabled: true + sqlserver.tempdb.contention.waiters.count: + enabled: true + sqlserver.tempdb.data_files.count: + enabled: true + sqlserver.tempdb.file.size: + enabled: true + sqlserver.tempdb.space.usage: + enabled: true + sqlserver.latch.superlatch.count: + enabled: true + sqlserver.latch.superlatch.transition.rate: + enabled: true + sqlserver.latch.wait.rate: + enabled: true + sqlserver.latch.wait_time.avg: + enabled: true + sqlserver.latch.wait_time.total: + enabled: true + sqlserver.index.search.rate: + enabled: true + sqlserver.resource_pool.disk.operations: + enabled: true + sqlserver.resource_pool.disk.throttled.read.rate: + enabled: true + sqlserver.resource_pool.disk.throttled.write.rate: + enabled: true + sqlserver.replica.data.rate: + enabled: true + sqlserver.failover_cluster.ag.cluster_type: + enabled: true + sqlserver.failover_cluster.ag.failure_condition_level: + enabled: true + sqlserver.failover_cluster.ag.health_check_timeout: + enabled: true + sqlserver.failover_cluster.ag.required_sync_secondaries: + enabled: true + sqlserver.failover_cluster.replica.database.queue_size: + enabled: true + sqlserver.failover_cluster.replica.database.redo.rate: + enabled: true + sqlserver.failover_cluster.replica.flow_control_time: + enabled: true + sqlserver.failover_cluster.replica.role: + enabled: true + sqlserver.failover_cluster.replica.synchronization_health: + enabled: true + sqlserver.computer.uptime: + enabled: true + sqlserver.cpu.count: + enabled: true + sqlserver.table.count: + enabled: true + + events: + db.server.query_sample: + enabled: true + db.server.top_query: + enabled: true + + top_query_collection: + lookback_time: 60s + max_query_sample_count: 500 + top_query_count: 200 + collection_interval: 60s + + collect_full_query_text: true + allowed_comment_keys: + - nr_service_guid + + query_sample_collection: + max_rows_per_query: 100 + + processors: + metrics_transform: + transforms: + - include: system.cpu.utilization + action: update + operations: + - action: aggregate_labels + label_set: [state] + aggregation_type: mean + - include: system.paging.operations + action: update + operations: + - action: aggregate_labels + label_set: [direction] + aggregation_type: sum + + filter/exclude_cpu_utilization: + metrics: + datapoint: + - 'metric.name == "system.cpu.utilization" and attributes["state"] == "interrupt"' + - 'metric.name == "system.cpu.utilization" and attributes["state"] == "nice"' + - 'metric.name == "system.cpu.utilization" and attributes["state"] == "softirq"' + + filter/exclude_memory_utilization: + metrics: + datapoint: + - 'metric.name == "system.memory.utilization" and attributes["state"] == "slab_unreclaimable"' + - 'metric.name == "system.memory.utilization" and attributes["state"] == "inactive"' + - 'metric.name == "system.memory.utilization" and attributes["state"] == "cached"' + - 'metric.name == "system.memory.utilization" and attributes["state"] == "buffered"' + - 'metric.name == "system.memory.utilization" and attributes["state"] == "slab_reclaimable"' + + filter/exclude_memory_usage: + metrics: + datapoint: + - 'metric.name == "system.memory.usage" and attributes["state"] == "slab_unreclaimable"' + - 'metric.name == "system.memory.usage" and attributes["state"] == "inactive"' + + filter/exclude_filesystem_utilization: + metrics: + datapoint: + - 'metric.name == "system.filesystem.utilization" and attributes["type"] == "squashfs"' + + filter/exclude_filesystem_usage: + metrics: + datapoint: + - 'metric.name == "system.filesystem.usage" and attributes["type"] == "squashfs"' + - 'metric.name == "system.filesystem.usage" and attributes["state"] == "reserved"' + + filter/exclude_filesystem_inodes_usage: + metrics: + datapoint: + - 'metric.name == "system.filesystem.inodes.usage" and attributes["type"] == "squashfs"' + - 'metric.name == "system.filesystem.inodes.usage" and attributes["state"] == "reserved"' + + filter/exclude_system_disk: + metrics: + datapoint: + - 'metric.name == "system.disk.operations" and IsMatch(attributes["device"], "^loop.*") == true' + - 'metric.name == "system.disk.merged" and IsMatch(attributes["device"], "^loop.*") == true' + - 'metric.name == "system.disk.io" and IsMatch(attributes["device"], "^loop.*") == true' + - 'metric.name == "system.disk.io_time" and IsMatch(attributes["device"], "^loop.*") == true' + - 'metric.name == "system.disk.operation_time" and IsMatch(attributes["device"], "^loop.*") == true' + + filter/exclude_system_paging: + metrics: + datapoint: + - 'metric.name == "system.paging.usage" and attributes["state"] == "cached"' + - 'metric.name == "system.paging.operations" and attributes["type"] == "cached"' + + filter/exclude_network: + metrics: + datapoint: + - 'IsMatch(metric.name, "^system.network.*") == true and attributes["device"] == "lo"' + + attributes/exclude_system_paging: + include: + match_type: strict + metric_names: + - system.paging.operations + actions: + - key: type + action: delete + + cumulativetodelta: + + transform/host: + metric_statements: + - context: metric + statements: + - set(metric.description, "") + - set(metric.unit, "") + + batch: + + resource_detection: + detectors: ["system"] + system: + hostname_sources: ["os"] + resource_attributes: + host.id: + enabled: true + + resource_detection/cloud: + detectors: ["gcp", "ec2", "azure"] + timeout: 2s + override: true + + resource_detection/env: + detectors: ["env"] + timeout: 2s + override: true + + exporters: + otlphttp: + endpoint: __OTLP_ENDPOINT__ + headers: + api-key: __LICENSE_KEY__ + tls: + insecure: false + compression: gzip + + service: + telemetry: + metrics: + level: none + + extensions: [health_check] + + pipelines: + metrics/host: + receivers: [host_metrics, nrsqlserver] + processors: + - metrics_transform + - filter/exclude_cpu_utilization + - filter/exclude_memory_utilization + - filter/exclude_memory_usage + - filter/exclude_filesystem_utilization + - filter/exclude_filesystem_usage + - filter/exclude_filesystem_inodes_usage + - filter/exclude_system_disk + - filter/exclude_network + - attributes/exclude_system_paging + - transform/host + - resource_detection + - resource_detection/cloud + - resource_detection/env + - cumulativetodelta + - batch + exporters: [otlphttp] + + logs/host: + receivers: [nrsqlserver] + processors: + - resource_detection + - resource_detection/cloud + - resource_detection/env + - batch + exporters: [otlphttp] + + traces: + receivers: [otlp] + processors: [resource_detection, resource_detection/cloud, resource_detection/env, batch] + exporters: [otlphttp] + + metrics: + receivers: [otlp] + processors: [resource_detection, resource_detection/cloud, resource_detection/env, batch] + exporters: [otlphttp] + + logs: + receivers: [otlp] + processors: [resource_detection, resource_detection/cloud, resource_detection/env, batch] + exporters: [otlphttp] + '@ + } else { + $template = @' + receivers: + nrsqlserver: + __RECEIVER_FIELDS__ + metrics: + sqlserver.database.count: + enabled: true + sqlserver.database.io: + enabled: true + sqlserver.database.latency: + enabled: true + sqlserver.database.operations: + enabled: true + sqlserver.database.tempdb.space: + enabled: true + sqlserver.database.tempdb.version_store.size: + enabled: true + sqlserver.deadlock.rate: + enabled: true + sqlserver.os.wait.duration: + enabled: true + sqlserver.processes.blocked: + enabled: true + sqlserver.memory.grants.pending.count: + enabled: true + sqlserver.database.file.size: + enabled: true + sqlserver.memory.area: + enabled: true + + events: + db.server.query_sample: + enabled: true + db.server.top_query: + enabled: true + + top_query_collection: + lookback_time: 60s + max_query_sample_count: 1000 + top_query_count: 250 + collection_interval: 60s + + collect_full_query_text: true + allowed_comment_keys: + - nr_service_guid + + query_sample_collection: + max_rows_per_query: 100 + + processors: + memory_limiter: + check_interval: ${env:NR_MEM_LIMITER_CHECK_INTERVAL:-1s} + limit_mib: ${env:NR_MEM_LIMITER_LIMIT_MIB:-200} + spike_limit_mib: ${env:NR_MEM_LIMITER_SPIKE_MIB:-50} + + batch: + + exporters: + otlphttp: + endpoint: __OTLP_ENDPOINT__ + headers: + api-key: __LICENSE_KEY__ + tls: + insecure: false + compression: gzip + + service: + telemetry: + metrics: + level: none + + pipelines: + metrics: + receivers: [nrsqlserver] + processors: [memory_limiter, batch] + exporters: [otlphttp] + + logs: + receivers: [nrsqlserver] + processors: [memory_limiter, batch] + exporters: [otlphttp] + '@ + } + + $configText = $template.Replace('__RECEIVER_FIELDS__', $receiverFields).Replace('__OTLP_ENDPOINT__', $otlpEndpoint).Replace('__LICENSE_KEY__', $licenseKey) + $configText | Set-Content -Path $configPath -Encoding utf8 + + $serviceAccount = $null + if ($mode -eq "2") { + $serviceAccount = '{{.NR_CLI_MSSQL_WIN_ACCOUNT}}' + } elseif ($mode -eq "3") { + $serviceAccount = '{{.NR_CLI_MSSQL_GMSA_ACCOUNT}}' + } + + $grants = @("SYSTEM:(F)", "BUILTIN\Administrators:(F)") + if ($serviceAccount) { $grants += "${serviceAccount}:(R)" } + + icacls $configPath /inheritance:r /grant:r $grants | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to restrict permissions on $configPath." + exit 1 + } + + Write-Host "MSSQL OTel config written to $configPath" + PSEOF + + configure_service: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $service = "nrdot-collector" + $exePath = "C:\Program Files\nrdot-collector\nrdot-collector.exe" + $configPath = "C:\Program Files\nrdot-collector\mssql-config.yaml" + $regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\nrdot-collector" + + $existingImagePath = (Get-ItemProperty -Path $regPath -Name "ImagePath" -ErrorAction SilentlyContinue).ImagePath + + if ($existingImagePath -and $existingImagePath -match [regex]::Escape("mssql-config.yaml")) { + Write-Host "nrdot-collector service is already configured to use mssql-config.yaml." + } else { + $newImagePath = "`"$exePath`" --config `"$configPath`"" + Set-ItemProperty -Path $regPath -Name "ImagePath" -Value $newImagePath + Write-Host "nrdot-collector service ImagePath updated to use mssql-config.yaml." + } + + $validateOutput = & $exePath validate --config="$configPath" 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "mssql-config.yaml failed validation:" + Write-Host $validateOutput + exit 1 + } + Write-Host "mssql-config.yaml validated successfully." + PSEOF + + restart_nrdot: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + net stop nrdot-collector + net start nrdot-collector + PSEOF + + assert_nrdot_status_ok: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $maxRetries = 30 + $tries = 0 + Write-Host "Waiting for NRDOT Collector to start..." + while ($tries -lt $maxRetries) { + $tries++ + $service = Get-Service -Name "nrdot-collector" -ErrorAction SilentlyContinue + if ($service -and $service.Status -eq "Running") { + Write-Host "NRDOT Collector is running." + exit 0 + } + Start-Sleep -Seconds 2 + } + Write-Host -ForegroundColor Red "NRDOT Collector did not start in time. Install log:" + Get-Content "$env:TEMP\nrdot_install.log" -ErrorAction SilentlyContinue | Select-Object -Last 50 + exit 31 + PSEOF + + default: + cmds: + - task: write_recipe_metadata + - task: assert_pre_req + - task: assert_auth_inputs + - task: assert_sql_server_version + - task: install_nrdot + - task: configure_database_user + - task: configure_service_identity + - task: create_collector_config + - task: configure_service + - task: restart_nrdot + - task: assert_nrdot_status_ok + +postInstall: + info: |2 + MSSQL OTel config: C:\Program Files\nrdot-collector\mssql-config.yaml + Service status: Get-Service nrdot-collector + Restart service: net stop nrdot-collector; net start nrdot-collector diff --git a/recipes/newrelic/infrastructure/nrdot/mssql-otel/windows.yml b/recipes/newrelic/infrastructure/nrdot/mssql-otel/windows.yml new file mode 100644 index 000000000..b1175a1c2 --- /dev/null +++ b/recipes/newrelic/infrastructure/nrdot/mssql-otel/windows.yml @@ -0,0 +1,1028 @@ +# Visit our schema definition for additional information on this file format. +# https://github.com/newrelic/open-install-library/blob/main/docs/recipe-spec/recipe-spec.md#schema-definition + +name: nrdot-collector-mssql +displayName: NRDOT MSSQL (Windows) +description: New Relic install recipe for MSSQL monitoring via NRDOT Collector on Windows self-hosted environments +repository: https://github.com/newrelic/nrdot-collector-releases + +installTargets: + - type: host + os: windows + +keywords: + - MSSQL + - SQL Server + - NRDOT + - OpenTelemetry + - OTel + - Database + - Windows + +processMatch: + - sqlservr + +preInstall: + discoveryMode: + - targeted + info: | + To capture data from SQL Server, we need to create/authorize a monitoring identity + (a SQL login, a Windows account, or a gMSA account depending on the authentication + method you choose). This requires administrative access to your SQL Server instance + (sysadmin role or equivalent) on the account running this installation. + +inputVars: + - name: NR_CLI_MSSQL_CONFIG_PRESET + prompt: "NRDOT configuration - 1) Standard 2) Full-feature: " + default: "1" + - name: NR_CLI_MSSQL_AUTH_MODE + prompt: "SQL Server authentication - 1) SQL Server Auth 2) Windows Auth 3) gMSA: " + default: "1" + - name: NR_CLI_MSSQL_SERVER + prompt: "SQL Server host (e.g. localhost): " + default: "localhost" + - name: NR_CLI_MSSQL_PORT + prompt: "SQL Server port: " + default: "1433" + - name: NR_CLI_MSSQL_SA_PASSWORD + prompt: "SQL Server 'sa' password (mode 1 only, used once to create the newrelic login; leave blank otherwise): " + secret: true + default: " " + - name: NR_CLI_MSSQL_LOGIN_NAME + prompt: "Monitoring username: " + default: "newrelic" + - name: NR_CLI_MSSQL_WIN_ACCOUNT + prompt: "Windows account to grant permissions to, DOMAIN\\username (mode 2 only; leave blank otherwise): " + default: " " + - name: NR_CLI_MSSQL_WIN_PASSWORD + prompt: "Password for that Windows account (mode 2 only; leave blank otherwise): " + secret: true + default: " " + - name: NR_CLI_MSSQL_GMSA_ACCOUNT + prompt: "gMSA account, DOMAIN\\gMSAName$ (mode 3 only; leave blank otherwise): " + default: " " + +validationNrql: "SELECT count(*) FROM Metric WHERE metricName LIKE 'sqlserver.%' AND instrumentation.provider = 'opentelemetry' SINCE 10 minutes ago" + +successLinkConfig: + type: EXPLORER + +install: + version: "3" + silent: true + + tasks: + write_recipe_metadata: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $metadata = '{"Metadata":{"CapturedCliOutput":"true"}}' + try { $metadata | Set-Content {{.NR_CLI_OUTPUT}} } catch {} + PSEOF + + assert_pre_req: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) + $isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + if (-not $isAdmin) { + Write-Host -ForegroundColor Red "This newrelic install must be run in an Administrator PowerShell session." + exit 131 + } + + $sqlcmdPath = Get-Command sqlcmd.exe -ErrorAction SilentlyContinue + if (-not $sqlcmdPath) { + Write-Host -ForegroundColor Red "sqlcmd is required to configure SQL Server monitoring. Install the sqlcmd utility (https://learn.microsoft.com/sql/tools/sqlcmd/sqlcmd-utility) and re-run this recipe." + exit 16 + } + PSEOF + + assert_auth_inputs: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + + switch ($mode) { + "1" { + $saPassword = '{{.NR_CLI_MSSQL_SA_PASSWORD}}' + if ([string]::IsNullOrWhiteSpace($saPassword) -or $saPassword.Length -eq 0) { + Write-Host -ForegroundColor Red "Error: SQL Server 'sa' password is required for SQL Server Auth (mode 1)." + Write-Host -ForegroundColor Red "Please re-run the installation and provide a valid SA password." + exit 1 + } + } + "2" { + $winAccount = '{{.NR_CLI_MSSQL_WIN_ACCOUNT}}' + $winPassword = '{{.NR_CLI_MSSQL_WIN_PASSWORD}}' + if ([string]::IsNullOrWhiteSpace($winAccount) -or $winAccount.Trim() -eq "" -or [string]::IsNullOrWhiteSpace($winPassword) -or $winPassword.Trim() -eq "") { + Write-Host -ForegroundColor Red "Error: Windows account and password are both required for Windows Auth (mode 2)." + Write-Host -ForegroundColor Red "Please re-run the installation and provide valid credentials." + exit 1 + } + } + "3" { + $gmsaAccount = '{{.NR_CLI_MSSQL_GMSA_ACCOUNT}}' + if ([string]::IsNullOrWhiteSpace($gmsaAccount) -or $gmsaAccount.Trim() -eq "") { + Write-Host -ForegroundColor Red "Error: gMSA account is required for gMSA Auth (mode 3)." + Write-Host -ForegroundColor Red "Please re-run the installation and provide a valid gMSA account." + exit 1 + } + } + default { + Write-Host -ForegroundColor Red "Error: NR_CLI_MSSQL_AUTH_MODE must be 1, 2, or 3." + exit 1 + } + } + PSEOF + + assert_sql_server_version: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + $server = '{{.NR_CLI_MSSQL_SERVER}}' + $port = '{{.NR_CLI_MSSQL_PORT}}' + + if ($mode -eq "1") { + $saPassword = '{{.NR_CLI_MSSQL_SA_PASSWORD}}' + if ([string]::IsNullOrEmpty($saPassword)) { + Write-Host -ForegroundColor Red "SA password is empty when preparing to connect to SQL Server - this should not happen." + exit 1 + } + $env:SQLCMDPASSWORD = $saPassword + $connArgs = @("-S", "$server,$port", "-U", "sa", "-C") + } else { + $connArgs = @("-S", "$server,$port", "-E", "-C") + } + + $query = "SET NOCOUNT ON; SELECT SERVERPROPERTY('ProductMajorVersion');" + $result = & sqlcmd @connArgs -b -h -1 -Q $query 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to connect to SQL Server to check its version:" + Write-Host $result + exit 1 + } + + $majorVersionLine = ($result | Where-Object { $_ -match '^\s*\d+\s*$' } | Select-Object -First 1) + $majorVersion = if ($majorVersionLine) { [int]$majorVersionLine.Trim() } else { $null } + + if (-not $majorVersion -or $majorVersion -lt 14) { + Write-Host -ForegroundColor Red "SQL Server version $majorVersion is not supported. SQL Server 2017 or later (major version 14+) is required." + exit 1 + } + + Write-Host "SQL Server major version $majorVersion detected - supported." + PSEOF + + install_nrdot: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + [Net.ServicePointManager]::SecurityProtocol = "tls12, tls" + + $CollectorService = "nrdot-collector" + + $LatestVersion = (Invoke-RestMethod -Uri "https://api.github.com/repos/newrelic/nrdot-collector-releases/releases/latest").tag_name + if (-not $LatestVersion) { + Write-Host -ForegroundColor Red "Failed to fetch the latest release version from GitHub. Please check your internet connection." + exit 1 + } + + $ExistingService = Get-Service -Name $CollectorService -ErrorAction SilentlyContinue + + if ($ExistingService) { + Write-Host -ForegroundColor Yellow "NRDOT Collector is already installed." + Write-Host "" + Write-Host "Please follow the steps below to complete the setup:" + Write-Host "" + Write-Host " 1. Create mssql-config.yaml" + Write-Host " if not present. Refer to:" + Write-Host " https://docs.newrelic.com/docs/opentelemetry/database/mssql/windows-hosted/" + Write-Host "" + Write-Host " 2. Make sure the monitoring user is created" + Write-Host " and has the required permissions granted." + Write-Host "" + Write-Host " 3. After making the above changes, restart the NRDOT Collector:" + Write-Host " net stop nrdot-collector; net start nrdot-collector" + Write-Host "" + exit 131 + } + + Write-Host "Installing nrdot-collector version: $LatestVersion" + + $MsiPath = "$env:TEMP\nrdot-collector.msi" + $LogPath = "$env:TEMP\nrdot_install.log" + $DownloadUrl = "https://github.com/newrelic/nrdot-collector-releases/releases/download/$LatestVersion/nrdot-collector_${LatestVersion}_windows_x64.msi" + Write-Host "Downloading from: $DownloadUrl" + + $WebClient = New-Object System.Net.WebClient + $WebClient.Headers.Add("User-Agent", "Mozilla/5.0") + if ($env:HTTPS_PROXY) { + $WebClient.Proxy = New-Object System.Net.WebProxy($env:HTTPS_PROXY, $true) + } + try { + $WebClient.DownloadFile($DownloadUrl, $MsiPath) + } catch { + Write-Host -ForegroundColor Red "Failed to download the nrdot-collector package: $_" + exit 1 + } + + $Process = Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$MsiPath`" /qn /norestart /L*V `"$LogPath`"" -Wait -NoNewWindow -PassThru + if ($Process.ExitCode -ne 0) { + Write-Host -ForegroundColor Red "msiexec failed with exit code $($Process.ExitCode). See $LogPath for details." + exit $Process.ExitCode + } + + Remove-Item -Path $MsiPath -ErrorAction SilentlyContinue + + $Service = Get-Service -Name $CollectorService -ErrorAction SilentlyContinue + if (-not $Service) { + Write-Host -ForegroundColor Red "nrdot-collector service was not found after installation." + exit 1 + } + + Write-Host "nrdot-collector installed successfully." + PSEOF + + configure_database_user_sqlauth: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + if ($mode -ne "1") { + exit 0 + } + $server = '{{.NR_CLI_MSSQL_SERVER}}' + $port = '{{.NR_CLI_MSSQL_PORT}}' + $sqlFile = "$env:TEMP\nr-mssql-grant.sql" + + function Invoke-GrantScript($connArgs, $file) { + $result = & sqlcmd @connArgs -b -i $file 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "SQL script failed:" + Write-Host $result + exit 1 + } + Write-Host $result + } + + function Protect-TempFile($path) { + icacls $path /inheritance:r /grant:r "SYSTEM:(F)" "BUILTIN\Administrators:(F)" | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to restrict permissions on $path." + exit 1 + } + } + + $loginName = '{{.NR_CLI_MSSQL_LOGIN_NAME}}' + $chars = (48..57) + (65..90) + (97..122) + $NrPassword = -join ($chars | Get-Random -Count 24 | ForEach-Object {[char]$_}) + + $sql = "USE [master];`nGO`nIF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = '$loginName')`n CREATE LOGIN [$loginName] WITH PASSWORD = '$NrPassword';`nELSE`n ALTER LOGIN [$loginName] WITH PASSWORD = '$NrPassword';`nGO`nGRANT VIEW SERVER STATE TO [$loginName];`nGRANT VIEW ANY DEFINITION TO [$loginName];`nGRANT VIEW ANY DATABASE TO [$loginName];`nGO`nDECLARE @name SYSNAME;`nDECLARE db_cursor CURSOR READ_ONLY FORWARD_ONLY FOR`nSELECT [name]`nFROM [master].[sys].[databases]`nWHERE [name] NOT IN ('master', 'msdb', 'model', 'rdsadmin', 'distribution')`nAND [state] = 0;`nOPEN db_cursor;`nFETCH NEXT FROM db_cursor INTO @name;`nWHILE @@FETCH_STATUS = 0`nBEGIN`n BEGIN TRY`n EXEC('USE [' + @name + '];`n IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = ''$loginName'')`n BEGIN`n CREATE USER [$loginName] FOR LOGIN [$loginName];`n END;`n GRANT VIEW DATABASE STATE TO [$loginName];');`n END TRY`n BEGIN CATCH`n PRINT 'Error on ' + @name + ': ' + ERROR_MESSAGE();`n END CATCH`n FETCH NEXT FROM db_cursor INTO @name;`nEND`nCLOSE db_cursor;`nDEALLOCATE db_cursor;`nGO" + $sql | Set-Content -Path $sqlFile + Protect-TempFile $sqlFile + $saPassword = '{{.NR_CLI_MSSQL_SA_PASSWORD}}' + if ([string]::IsNullOrEmpty($saPassword)) { + Write-Host -ForegroundColor Red "SA password is empty when preparing to connect to SQL Server - this should not happen." + exit 1 + } + $env:SQLCMDPASSWORD = $saPassword + Invoke-GrantScript @("-S", "$server,$port", "-U", "sa", "-C") $sqlFile + Write-Host "SQL Server monitoring identity configured successfully." + Remove-Item -Path $sqlFile -ErrorAction SilentlyContinue + PSEOF + + configure_database_user_winauth: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + if ($mode -ne "2") { + exit 0 + } + $server = '{{.NR_CLI_MSSQL_SERVER}}' + $port = '{{.NR_CLI_MSSQL_PORT}}' + $sqlFile = "$env:TEMP\nr-mssql-grant.sql" + + function Invoke-GrantScript($connArgs, $file) { + $result = & sqlcmd @connArgs -b -i $file 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "SQL script failed:" + Write-Host $result + exit 1 + } + Write-Host $result + } + + function Protect-TempFile($path) { + icacls $path /inheritance:r /grant:r "SYSTEM:(F)" "BUILTIN\Administrators:(F)" | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to restrict permissions on $path." + exit 1 + } + } + + $account = '{{.NR_CLI_MSSQL_WIN_ACCOUNT}}' + $sql = "USE [master];`nGO`nGRANT VIEW SERVER STATE TO [$account];`nGRANT VIEW ANY DEFINITION TO [$account];`nGRANT VIEW ANY DATABASE TO [$account];`nGO`nDECLARE @name SYSNAME;`nDECLARE @sql NVARCHAR(MAX);`nDECLARE db_cursor CURSOR READ_ONLY FORWARD_ONLY FOR`nSELECT [name]`nFROM [master].[sys].[databases]`nWHERE [name] NOT IN ('master', 'msdb', 'model', 'rdsadmin', 'distribution')`n AND [state] = 0;`nOPEN db_cursor;`nFETCH NEXT FROM db_cursor INTO @name;`nWHILE @@FETCH_STATUS = 0`nBEGIN`n BEGIN TRY`n PRINT 'Granting permissions on database: ' + @name;`n SET @sql = '`n USE [' + @name + '];`n IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = ''$account'')`n BEGIN`n CREATE USER [$account] FOR LOGIN [$account];`n END;`n GRANT VIEW DATABASE STATE TO [$account];`n GRANT VIEW DEFINITION TO [$account];`n ALTER ROLE db_datareader ADD MEMBER [$account];';`n EXEC sp_executesql @sql;`n PRINT 'Success: ' + @name;`n END TRY`n BEGIN CATCH`n PRINT 'Error on ' + @name + ': ' + ERROR_MESSAGE();`n END CATCH`n FETCH NEXT FROM db_cursor INTO @name;`nEND`nCLOSE db_cursor;`nDEALLOCATE db_cursor;`nGO" + $sql | Set-Content -Path $sqlFile + Protect-TempFile $sqlFile + Invoke-GrantScript @("-S", "$server,$port", "-E", "-C") $sqlFile + Write-Host "SQL Server monitoring identity configured successfully." + Remove-Item -Path $sqlFile -ErrorAction SilentlyContinue + PSEOF + + configure_database_user_gmsa: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + if ($mode -ne "3") { + exit 0 + } + $server = '{{.NR_CLI_MSSQL_SERVER}}' + $port = '{{.NR_CLI_MSSQL_PORT}}' + $sqlFile = "$env:TEMP\nr-mssql-grant.sql" + + function Invoke-GrantScript($connArgs, $file) { + $result = & sqlcmd @connArgs -b -i $file 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "SQL script failed:" + Write-Host $result + exit 1 + } + Write-Host $result + } + + function Protect-TempFile($path) { + icacls $path /inheritance:r /grant:r "SYSTEM:(F)" "BUILTIN\Administrators:(F)" | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to restrict permissions on $path." + exit 1 + } + } + + $gmsa = '{{.NR_CLI_MSSQL_GMSA_ACCOUNT}}' + $sql = "USE master;`nGO`nIF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = '$gmsa')`n CREATE LOGIN [$gmsa] FROM WINDOWS;`nGO`nGRANT VIEW SERVER STATE TO [$gmsa];`nGRANT VIEW ANY DEFINITION TO [$gmsa];`nGRANT VIEW ANY DATABASE TO [$gmsa];`nGO`nDECLARE @name SYSNAME;`nDECLARE db_cursor CURSOR READ_ONLY FORWARD_ONLY FOR`nSELECT [name] FROM [master].[sys].[databases]`nWHERE [name] NOT IN ('master','msdb','model','rdsadmin','distribution')`nAND [state] = 0;`nOPEN db_cursor;`nFETCH NEXT FROM db_cursor INTO @name;`nWHILE @@FETCH_STATUS = 0`nBEGIN`n BEGIN TRY`n EXEC('USE [' + @name + '];`n IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = ''$gmsa'')`n BEGIN`n CREATE USER [$gmsa] FOR LOGIN [$gmsa];`n END;`n GRANT VIEW DATABASE STATE TO [$gmsa];');`n END TRY`n BEGIN CATCH`n PRINT 'Error on ' + @name + ': ' + ERROR_MESSAGE();`n END CATCH`n FETCH NEXT FROM db_cursor INTO @name;`nEND`nCLOSE db_cursor;`nDEALLOCATE db_cursor;`nGO" + $sql | Set-Content -Path $sqlFile + Protect-TempFile $sqlFile + Invoke-GrantScript @("-S", "$server,$port", "-E", "-C") $sqlFile + Write-Host "SQL Server monitoring identity configured successfully." + Remove-Item -Path $sqlFile -ErrorAction SilentlyContinue + PSEOF + + configure_service_identity: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + $service = "nrdot-collector" + + if ($mode -eq "1") { + Write-Host "SQL Server Auth selected - service logon account left unchanged (LocalSystem)." + exit 0 + } + + if ($mode -eq "2") { + $account = '{{.NR_CLI_MSSQL_WIN_ACCOUNT}}' + $password = '{{.NR_CLI_MSSQL_WIN_PASSWORD}}' + } else { + $account = '{{.NR_CLI_MSSQL_GMSA_ACCOUNT}}' + $password = "" + } + + Stop-Service -Name $service -Force -ErrorAction SilentlyContinue + + & sc.exe config "$service" obj= "$account" password= "$password" + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to configure the nrdot-collector service to run as $account." + exit 1 + } + + $svcInfo = Get-WmiObject Win32_Service -Filter "Name='$service'" + Write-Host "nrdot-collector service logon account set to: $($svcInfo.StartName)" + + Start-Service -Name $service + PSEOF + + create_collector_config: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $mode = "{{.NR_CLI_MSSQL_AUTH_MODE}}" + $preset = "{{.NR_CLI_MSSQL_CONFIG_PRESET}}" + $server = '{{.NR_CLI_MSSQL_SERVER}}' + $port = '{{.NR_CLI_MSSQL_PORT}}' + $region = "{{.NEW_RELIC_REGION}}" + $licenseKey = "{{.NEW_RELIC_LICENSE_KEY}}" + $configDir = "C:\Program Files\nrdot-collector" + $configPath = "$configDir\mssql-config.yaml" + + New-Item -Path $configDir -ItemType Directory -Force | Out-Null + + $interval = if ($preset -eq "2") { "30s" } else { "15s" } + + if ($mode -eq "1") { + $loginName = '{{.NR_CLI_MSSQL_LOGIN_NAME}}' + $chars = (48..57) + (65..90) + (97..122) + $NrPassword = -join ($chars | Get-Random -Count 24 | ForEach-Object {[char]$_}) + $env:SQLCMDPASSWORD = '{{.NR_CLI_MSSQL_SA_PASSWORD}}' + $alterResult = & sqlcmd -S "$server,$port" -U sa -C -b -Q "ALTER LOGIN [$loginName] WITH PASSWORD = '$NrPassword';" 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to set the monitoring login's password:" + Write-Host $alterResult + exit 1 + } + $receiverFields = " collection_interval: $interval`n username: $loginName`n password: $NrPassword`n server: $server`n port: $port" + } else { + $receiverFields = " collection_interval: $interval`n datasource: `"server=$server;port=$port;integrated security=true;encrypt=true;TrustServerCertificate=true;`"" + } + + switch ($region) { + "staging" { $otlpEndpoint = "https://staging-otlp.nr-data.net" } + "EU" { $otlpEndpoint = "https://otlp.eu01.nr-data.net" } + "JP" { $otlpEndpoint = "https://otlp.jp.nr-data.net" } + default { $otlpEndpoint = "https://otlp.nr-data.net" } + } + + if ($preset -eq "2") { + $template = @' + extensions: + health_check: + + receivers: + otlp: + protocols: + grpc: + http: + + host_metrics: + collection_interval: 30s + scrapers: + cpu: + metrics: + system.cpu.time: + enabled: false + system.cpu.utilization: + enabled: true + load: + memory: + metrics: + system.memory.utilization: + enabled: true + paging: + metrics: + system.paging.utilization: + enabled: false + system.paging.faults: + enabled: false + disk: + metrics: + system.disk.merged: + enabled: false + system.disk.pending_operations: + enabled: false + system.disk.weighted_io_time: + enabled: false + network: + metrics: + system.network.connections: + enabled: false + processes: + process: + mute_process_name_error: true + mute_process_exe_error: true + mute_process_user_error: true + mute_process_io_error: true + metrics: + process.cpu.utilization: + enabled: true + process.memory.utilization: + enabled: true + + nrsqlserver: + __RECEIVER_FIELDS__ + metrics: + sqlserver.database.count: + enabled: true + sqlserver.database.file.size: + enabled: true + sqlserver.database.io: + enabled: true + sqlserver.database.latency: + enabled: true + sqlserver.database.operations: + enabled: true + sqlserver.database.transactions.active: + enabled: true + sqlserver.database.backup_or_restore.rate: + enabled: true + sqlserver.database.execution.errors: + enabled: true + sqlserver.database.full_scan.rate: + enabled: true + sqlserver.database.tempdb.space: + enabled: true + sqlserver.database.tempdb.version_store.size: + enabled: true + sqlserver.lock.timeout.rate: + enabled: true + sqlserver.lock.wait.count: + enabled: true + sqlserver.deadlock.rate: + enabled: true + sqlserver.transaction.delay: + enabled: true + sqlserver.transaction.longest_running_time: + enabled: true + sqlserver.transaction.version_cleanup.rate: + enabled: true + sqlserver.transaction.version_generation.rate: + enabled: true + sqlserver.memory.area: + enabled: true + sqlserver.memory.cache.object.count: + enabled: true + sqlserver.memory.grants.pending.count: + enabled: true + sqlserver.memory.page.count: + enabled: true + sqlserver.memory.usage: + enabled: true + sqlserver.os.memory.usage: + enabled: true + sqlserver.os.memory.utilization: + enabled: true + sqlserver.os.scheduler.runnable_tasks.count: + enabled: true + sqlserver.os.wait.duration: + enabled: true + sqlserver.os.wait.tasks.count: + enabled: true + sqlserver.page.buffer_cache.free_list.stalls.rate: + enabled: true + sqlserver.page.lookup.rate: + enabled: true + sqlserver.batch.compilation.utilization: + enabled: true + sqlserver.batch.page_split.utilization: + enabled: true + sqlserver.recompilation.ratio: + enabled: true + sqlserver.parameterization.rate: + enabled: true + sqlserver.plan.execution.rate: + enabled: true + sqlserver.attention.rate: + enabled: true + sqlserver.process.count: + enabled: true + sqlserver.processes.blocked: + enabled: true + sqlserver.login.rate: + enabled: true + sqlserver.logout.rate: + enabled: true + sqlserver.thread_pool.tasks.count: + enabled: true + sqlserver.thread_pool.workers.count: + enabled: true + sqlserver.thread_pool.workers.max: + enabled: true + sqlserver.thread_pool.workers.utilization: + enabled: true + sqlserver.tempdb.allocation.wait_time.total: + enabled: true + sqlserver.tempdb.contention.waiters.count: + enabled: true + sqlserver.tempdb.data_files.count: + enabled: true + sqlserver.tempdb.file.size: + enabled: true + sqlserver.tempdb.space.usage: + enabled: true + sqlserver.latch.superlatch.count: + enabled: true + sqlserver.latch.superlatch.transition.rate: + enabled: true + sqlserver.latch.wait.rate: + enabled: true + sqlserver.latch.wait_time.avg: + enabled: true + sqlserver.latch.wait_time.total: + enabled: true + sqlserver.index.search.rate: + enabled: true + sqlserver.resource_pool.disk.operations: + enabled: true + sqlserver.resource_pool.disk.throttled.read.rate: + enabled: true + sqlserver.resource_pool.disk.throttled.write.rate: + enabled: true + sqlserver.replica.data.rate: + enabled: true + sqlserver.failover_cluster.ag.cluster_type: + enabled: true + sqlserver.failover_cluster.ag.failure_condition_level: + enabled: true + sqlserver.failover_cluster.ag.health_check_timeout: + enabled: true + sqlserver.failover_cluster.ag.required_sync_secondaries: + enabled: true + sqlserver.failover_cluster.replica.database.queue_size: + enabled: true + sqlserver.failover_cluster.replica.database.redo.rate: + enabled: true + sqlserver.failover_cluster.replica.flow_control_time: + enabled: true + sqlserver.failover_cluster.replica.role: + enabled: true + sqlserver.failover_cluster.replica.synchronization_health: + enabled: true + sqlserver.computer.uptime: + enabled: true + sqlserver.cpu.count: + enabled: true + sqlserver.table.count: + enabled: true + + events: + db.server.query_sample: + enabled: true + db.server.top_query: + enabled: true + + top_query_collection: + lookback_time: 60s + max_query_sample_count: 500 + top_query_count: 200 + collection_interval: 60s + + collect_full_query_text: true + allowed_comment_keys: + - nr_service_guid + + query_sample_collection: + max_rows_per_query: 100 + + processors: + metrics_transform: + transforms: + - include: system.cpu.utilization + action: update + operations: + - action: aggregate_labels + label_set: [state] + aggregation_type: mean + - include: system.paging.operations + action: update + operations: + - action: aggregate_labels + label_set: [direction] + aggregation_type: sum + + filter/exclude_cpu_utilization: + metrics: + datapoint: + - 'metric.name == "system.cpu.utilization" and attributes["state"] == "interrupt"' + - 'metric.name == "system.cpu.utilization" and attributes["state"] == "nice"' + - 'metric.name == "system.cpu.utilization" and attributes["state"] == "softirq"' + + filter/exclude_memory_utilization: + metrics: + datapoint: + - 'metric.name == "system.memory.utilization" and attributes["state"] == "slab_unreclaimable"' + - 'metric.name == "system.memory.utilization" and attributes["state"] == "inactive"' + - 'metric.name == "system.memory.utilization" and attributes["state"] == "cached"' + - 'metric.name == "system.memory.utilization" and attributes["state"] == "buffered"' + - 'metric.name == "system.memory.utilization" and attributes["state"] == "slab_reclaimable"' + + filter/exclude_memory_usage: + metrics: + datapoint: + - 'metric.name == "system.memory.usage" and attributes["state"] == "slab_unreclaimable"' + - 'metric.name == "system.memory.usage" and attributes["state"] == "inactive"' + + filter/exclude_filesystem_utilization: + metrics: + datapoint: + - 'metric.name == "system.filesystem.utilization" and attributes["type"] == "squashfs"' + + filter/exclude_filesystem_usage: + metrics: + datapoint: + - 'metric.name == "system.filesystem.usage" and attributes["type"] == "squashfs"' + - 'metric.name == "system.filesystem.usage" and attributes["state"] == "reserved"' + + filter/exclude_filesystem_inodes_usage: + metrics: + datapoint: + - 'metric.name == "system.filesystem.inodes.usage" and attributes["type"] == "squashfs"' + - 'metric.name == "system.filesystem.inodes.usage" and attributes["state"] == "reserved"' + + filter/exclude_system_disk: + metrics: + datapoint: + - 'metric.name == "system.disk.operations" and IsMatch(attributes["device"], "^loop.*") == true' + - 'metric.name == "system.disk.merged" and IsMatch(attributes["device"], "^loop.*") == true' + - 'metric.name == "system.disk.io" and IsMatch(attributes["device"], "^loop.*") == true' + - 'metric.name == "system.disk.io_time" and IsMatch(attributes["device"], "^loop.*") == true' + - 'metric.name == "system.disk.operation_time" and IsMatch(attributes["device"], "^loop.*") == true' + + filter/exclude_system_paging: + metrics: + datapoint: + - 'metric.name == "system.paging.usage" and attributes["state"] == "cached"' + - 'metric.name == "system.paging.operations" and attributes["type"] == "cached"' + + filter/exclude_network: + metrics: + datapoint: + - 'IsMatch(metric.name, "^system.network.*") == true and attributes["device"] == "lo"' + + attributes/exclude_system_paging: + include: + match_type: strict + metric_names: + - system.paging.operations + actions: + - key: type + action: delete + + cumulativetodelta: + + transform/host: + metric_statements: + - context: metric + statements: + - set(metric.description, "") + - set(metric.unit, "") + + batch: + + resource_detection: + detectors: ["system"] + system: + hostname_sources: ["os"] + resource_attributes: + host.id: + enabled: true + + resource_detection/cloud: + detectors: ["gcp", "ec2", "azure"] + timeout: 2s + override: true + + resource_detection/env: + detectors: ["env"] + timeout: 2s + override: true + + exporters: + otlphttp: + endpoint: __OTLP_ENDPOINT__ + headers: + api-key: __LICENSE_KEY__ + tls: + insecure: false + compression: gzip + + service: + telemetry: + metrics: + level: none + + extensions: [health_check] + + pipelines: + metrics/host: + receivers: [host_metrics, nrsqlserver] + processors: + - metrics_transform + - filter/exclude_cpu_utilization + - filter/exclude_memory_utilization + - filter/exclude_memory_usage + - filter/exclude_filesystem_utilization + - filter/exclude_filesystem_usage + - filter/exclude_filesystem_inodes_usage + - filter/exclude_system_disk + - filter/exclude_network + - attributes/exclude_system_paging + - transform/host + - resource_detection + - resource_detection/cloud + - resource_detection/env + - cumulativetodelta + - batch + exporters: [otlphttp] + + logs/host: + receivers: [nrsqlserver] + processors: + - resource_detection + - resource_detection/cloud + - resource_detection/env + - batch + exporters: [otlphttp] + + traces: + receivers: [otlp] + processors: [resource_detection, resource_detection/cloud, resource_detection/env, batch] + exporters: [otlphttp] + + metrics: + receivers: [otlp] + processors: [resource_detection, resource_detection/cloud, resource_detection/env, batch] + exporters: [otlphttp] + + logs: + receivers: [otlp] + processors: [resource_detection, resource_detection/cloud, resource_detection/env, batch] + exporters: [otlphttp] + '@ + } else { + $template = @' + receivers: + nrsqlserver: + __RECEIVER_FIELDS__ + metrics: + sqlserver.database.count: + enabled: true + sqlserver.database.io: + enabled: true + sqlserver.database.latency: + enabled: true + sqlserver.database.operations: + enabled: true + sqlserver.database.tempdb.space: + enabled: true + sqlserver.database.tempdb.version_store.size: + enabled: true + sqlserver.deadlock.rate: + enabled: true + sqlserver.os.wait.duration: + enabled: true + sqlserver.processes.blocked: + enabled: true + sqlserver.memory.grants.pending.count: + enabled: true + sqlserver.database.file.size: + enabled: true + sqlserver.memory.area: + enabled: true + + events: + db.server.query_sample: + enabled: true + db.server.top_query: + enabled: true + + top_query_collection: + lookback_time: 60s + max_query_sample_count: 1000 + top_query_count: 250 + collection_interval: 60s + + collect_full_query_text: true + allowed_comment_keys: + - nr_service_guid + + query_sample_collection: + max_rows_per_query: 100 + + processors: + memory_limiter: + check_interval: ${env:NR_MEM_LIMITER_CHECK_INTERVAL:-1s} + limit_mib: ${env:NR_MEM_LIMITER_LIMIT_MIB:-200} + spike_limit_mib: ${env:NR_MEM_LIMITER_SPIKE_MIB:-50} + + batch: + + exporters: + otlphttp: + endpoint: __OTLP_ENDPOINT__ + headers: + api-key: __LICENSE_KEY__ + tls: + insecure: false + compression: gzip + + service: + telemetry: + metrics: + level: none + + pipelines: + metrics: + receivers: [nrsqlserver] + processors: [memory_limiter, batch] + exporters: [otlphttp] + + logs: + receivers: [nrsqlserver] + processors: [memory_limiter, batch] + exporters: [otlphttp] + '@ + } + + $configText = $template.Replace('__RECEIVER_FIELDS__', $receiverFields).Replace('__OTLP_ENDPOINT__', $otlpEndpoint).Replace('__LICENSE_KEY__', $licenseKey) + $configText | Set-Content -Path $configPath -Encoding utf8 + + $serviceAccount = $null + if ($mode -eq "2") { + $serviceAccount = '{{.NR_CLI_MSSQL_WIN_ACCOUNT}}' + } elseif ($mode -eq "3") { + $serviceAccount = '{{.NR_CLI_MSSQL_GMSA_ACCOUNT}}' + } + + $grants = @("SYSTEM:(F)", "BUILTIN\Administrators:(F)") + if ($serviceAccount) { $grants += "${serviceAccount}:(R)" } + + icacls $configPath /inheritance:r /grant:r $grants | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "Failed to restrict permissions on $configPath." + exit 1 + } + + Write-Host "MSSQL OTel config written to $configPath" + PSEOF + + configure_service: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $service = "nrdot-collector" + $exePath = "C:\Program Files\nrdot-collector\nrdot-collector.exe" + $configPath = "C:\Program Files\nrdot-collector\mssql-config.yaml" + $regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\nrdot-collector" + + $existingImagePath = (Get-ItemProperty -Path $regPath -Name "ImagePath" -ErrorAction SilentlyContinue).ImagePath + + if ($existingImagePath -and $existingImagePath -match [regex]::Escape("mssql-config.yaml")) { + Write-Host "nrdot-collector service is already configured to use mssql-config.yaml." + } else { + $newImagePath = "`"$exePath`" --config `"$configPath`"" + Set-ItemProperty -Path $regPath -Name "ImagePath" -Value $newImagePath + Write-Host "nrdot-collector service ImagePath updated to use mssql-config.yaml." + } + + $validateOutput = & $exePath validate --config="$configPath" 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host -ForegroundColor Red "mssql-config.yaml failed validation:" + Write-Host $validateOutput + exit 1 + } + Write-Host "mssql-config.yaml validated successfully." + PSEOF + + restart_nrdot: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + net stop nrdot-collector + net start nrdot-collector + PSEOF + + assert_nrdot_status_ok: + cmds: + - | + powershell -NoProfile -Command - <<'PSEOF' + $maxRetries = 30 + $tries = 0 + Write-Host "Waiting for NRDOT Collector to start..." + while ($tries -lt $maxRetries) { + $tries++ + $service = Get-Service -Name "nrdot-collector" -ErrorAction SilentlyContinue + if ($service -and $service.Status -eq "Running") { + Write-Host "NRDOT Collector is running." + exit 0 + } + Start-Sleep -Seconds 2 + } + Write-Host -ForegroundColor Red "NRDOT Collector did not start in time. Install log:" + Get-Content "$env:TEMP\nrdot_install.log" -ErrorAction SilentlyContinue | Select-Object -Last 50 + exit 31 + PSEOF + + default: + cmds: + - task: write_recipe_metadata + - task: assert_pre_req + - task: assert_auth_inputs + - task: assert_sql_server_version + - task: install_nrdot + - task: configure_database_user_sqlauth + - task: configure_database_user_winauth + - task: configure_database_user_gmsa + - task: configure_service_identity + - task: create_collector_config + - task: configure_service + - task: restart_nrdot + - task: assert_nrdot_status_ok + +postInstall: + info: |2 + MSSQL OTel config: C:\Program Files\nrdot-collector\mssql-config.yaml + Service status: Get-Service nrdot-collector + Restart service: net stop nrdot-collector; net start nrdot-collector \ No newline at end of file diff --git a/test/definitions/nrdot/mssql-otel/mssql-windows2019.json b/test/definitions/nrdot/mssql-otel/mssql-windows2019.json new file mode 100644 index 000000000..dd95c9fda --- /dev/null +++ b/test/definitions/nrdot/mssql-otel/mssql-windows2019.json @@ -0,0 +1,49 @@ +{ + "global_tags": { + "owning_team": "database-integrations", + "Environment": "development", + "Department": "product", + "Product": "database-integrations" + }, + "resources": [ + { + "id": "host1", + "provider": "aws", + "type": "ec2", + "size": "t3.xlarge", + "is_windows": true, + "ami_name": "Windows_Server-2019-English-Full-SQL_2019_Standard-*", + "user_name": "Administrator" + } + ], + "services": [ + { + "id": "mssql1", + "destinations": [ + "host1" + ], + "source_repository": "https://github.com/newrelic/open-install-library.git", + "deploy_script_path": "test/deploy/windows/nrdot-mssql/install/roles", + "port": 1433 + } + ], + "instrumentations": { + "resources": [ + { + "id": "nr_nrdot_mssql_windows2019", + "resource_ids": [ + "host1" + ], + "provider": "newrelic", + "source_repository": "https://github.com/newrelic/open-install-library.git", + "deploy_script_path": "test/deploy/windows/newrelic-cli/install-recipe/roles", + "params": { + "recipe_content_url": "https://raw.githubusercontent.com/newrelic/open-install-library/main/recipes/newrelic/infrastructure/nrdot/mssql-otel/windows.yml", + "recipe_targeted": "nrdot-collector-mssql", + "validate_output": "NRDOT MSSQL \\(Windows\\)\\s+\\((installed|available)\\)", + "env_var": "$env:NR_CLI_MSSQL_CONFIG_PRESET=\"1\"; $env:NR_CLI_MSSQL_AUTH_MODE=\"1\"; $env:NR_CLI_MSSQL_SERVER=\"localhost\"; $env:NR_CLI_MSSQL_PORT=\"1433\"; $env:NR_CLI_MSSQL_SA_PASSWORD=\"YourStrong@Passw0rd\"; $env:NR_CLI_MSSQL_LOGIN_NAME=\"newrelic\"; $env:NR_CLI_MSSQL_WIN_ACCOUNT=\"\"; $env:NR_CLI_MSSQL_WIN_PASSWORD=\"\"; $env:NR_CLI_MSSQL_GMSA_ACCOUNT=\"\"" + } + } + ] + } +} diff --git a/test/deploy/windows/newrelic-cli/install-recipe/roles/onafterstart/tasks/main.yml b/test/deploy/windows/newrelic-cli/install-recipe/roles/onafterstart/tasks/main.yml index 3c02d69d5..1a8239ae2 100644 --- a/test/deploy/windows/newrelic-cli/install-recipe/roles/onafterstart/tasks/main.yml +++ b/test/deploy/windows/newrelic-cli/install-recipe/roles/onafterstart/tasks/main.yml @@ -11,6 +11,7 @@ # newrelic_organization_id: (optional) The NewRelic Organization ID if use_organization_id is set to True. # nr_host_fleet_id: (optional) A NewRelic host fleet id to add if NR_CLI_FLEET_ID should be added to env_vars. # use_system_identity_auth: (optional) A boolean value to control if system identity authentication should be used. False by default. +# env_var: (optional) A ";"-joined string of PowerShell $env: assignments (e.g. '$env:FOO="bar"; $env:BAZ="qux"') injected into the environment before running newrelic install. - name: Prepare command_should_fail option when: command_should_fail is not defined @@ -47,6 +48,11 @@ set_fact: env_vars: "" +- name: Get environment variables from "env_var" + set_fact: + env_vars: "{{ env_vars }}; {{ env_var }}" + when: env_var is defined + - name: Adding newrelic accountId environment variable set_fact: env_vars: "{{ env_vars }}; $env:NEW_RELIC_ACCOUNT_ID=\"{{ newrelic_account_id }}\"" diff --git a/test/deploy/windows/nrdot-mssql/install/roles/configure/tasks/main.yml b/test/deploy/windows/nrdot-mssql/install/roles/configure/tasks/main.yml new file mode 100644 index 000000000..94661981b --- /dev/null +++ b/test/deploy/windows/nrdot-mssql/install/roles/configure/tasks/main.yml @@ -0,0 +1,48 @@ +--- + +- name: Verify SQL Server is installed and responsive + win_shell: | + sqlcmd -S localhost -I -Q "SELECT GETDATE()"; + register: output + +- fail: + msg: "SqlServer is not installed on the host" + when: output is failed + +- name: Set SqlServer Authentication mode to Mixed to allow username/password login + win_shell: | + Import-Module -Name SQLPS + $sql = [Microsoft.SqlServer.Management.Smo.Server]::new("localhost") + $sql.Settings.LoginMode = 'Mixed' + $sql.Alter() + Get-Service -Name 'MSSQLSERVER' | Restart-Service -Force + +- name: Wait for SQL Server to accept connections after restart + win_shell: | + sqlcmd -S localhost -I -Q "SELECT 1"; + register: output + retries: 30 + delay: 2 + until: output is not failed + +- fail: + msg: "SQL Server did not come back up after switching to Mixed Mode auth" + when: output is failed + +- name: Enable sa login and set its password + win_shell: | + sqlcmd -S localhost -I -Q "ALTER LOGIN sa WITH PASSWORD='YourStrong@Passw0rd'; ALTER LOGIN sa ENABLE;"; + register: output + +- fail: + msg: "Failed to enable/set password for the sa login" + when: output is failed + +- name: Verify sa login works + win_shell: | + sqlcmd -S localhost -U sa -P "YourStrong@Passw0rd" -Q "SELECT GETDATE()"; + register: output + +- fail: + msg: "Unable to authenticate as sa with the configured password" + when: output is failed