packages feed

hs-opentelemetry-sdk-1.0.0.0: src/OpenTelemetry/Resource/OperatingSystem/Detector.hs

{-# LANGUAGE OverloadedStrings #-}

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

{- |
 Module      :  OpenTelemetry.Resource.OperatingSystem.Detector
 Copyright   :  (c) Ian Duncan, 2021
 License     :  BSD-3
 Maintainer  :  Ian Duncan
 Stability   :  experimental
 Portability :  non-portable (GHC extensions)

 Detect information about the current system's OS.
-}
module OpenTelemetry.Resource.OperatingSystem.Detector (
  detectOperatingSystem,
) where

import Control.Exception (try)
import Data.Maybe (mapMaybe)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import OpenTelemetry.Resource.OperatingSystem
import System.IO.Error (tryIOError)
import System.Info (os)
import System.Process (readProcess)


{- | Retrieve any information able to be detected about the current operating system.

 Detects 'osType', 'osName', 'osVersion', and 'osDescription' on macOS and Linux.
 On other platforms, only 'osType' is populated.

 @since 0.0.1.0
-}
detectOperatingSystem :: IO OperatingSystem
detectOperatingSystem = do
  let osTypeVal =
        if os == "mingw32"
          then "windows"
          else T.pack os
  (name, ver, desc) <- detectOsDetails osTypeVal
  pure
    OperatingSystem
      { osType = osTypeVal
      , osDescription = desc
      , osName = name
      , osVersion = ver
      , osBuildId = Nothing
      }


detectOsDetails :: T.Text -> IO (Maybe T.Text, Maybe T.Text, Maybe T.Text)
detectOsDetails "darwin" = do
  name <- tryShellCommand "sw_vers" ["-productName"]
  ver <- tryShellCommand "sw_vers" ["-productVersion"]
  let desc = case (name, ver) of
        (Just n, Just v) -> Just (n <> " " <> v)
        _ -> Nothing
  pure (name, ver, desc)
detectOsDetails "linux" = do
  osRelease <- tryReadOsRelease
  case osRelease of
    Nothing -> pure (Nothing, Nothing, Nothing)
    Just content ->
      let fields = parseOsRelease content
          name = lookup "NAME" fields
          ver = lookup "VERSION_ID" fields
          desc = lookup "PRETTY_NAME" fields
      in pure (name, ver, desc)
detectOsDetails "windows" =
  pure (Just "Windows", Nothing, Nothing)
detectOsDetails _ =
  pure (Nothing, Nothing, Nothing)


tryShellCommand :: String -> [String] -> IO (Maybe T.Text)
tryShellCommand cmd args = do
  result <- try (readProcess cmd args "") :: IO (Either IOError String)
  pure $ case result of
    Right output ->
      let trimmed = T.strip (T.pack output)
      in if T.null trimmed then Nothing else Just trimmed
    Left _ -> Nothing


tryReadOsRelease :: IO (Maybe T.Text)
tryReadOsRelease = do
  result <- tryIOError (T.readFile "/etc/os-release")
  pure $ case result of
    Right content | not (T.null content) -> Just content
    _ -> Nothing


parseOsRelease :: T.Text -> [(T.Text, T.Text)]
parseOsRelease content =
  mapMaybe parseLine (T.lines content)
  where
    parseLine line =
      case T.break (== '=') line of
        (key, rest)
          | not (T.null rest) ->
              Just (key, stripQuotes (T.drop 1 rest))
        _ -> Nothing
    stripQuotes t =
      case T.uncons t of
        Just ('"', rest) -> maybe rest fst (T.unsnoc rest)
        _ -> t