internalLogging.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { readFile } from 'fs/promises'
  2. import memoize from 'lodash-es/memoize.js'
  3. import type { ToolPermissionContext } from '../Tool.js'
  4. import { jsonStringify } from '../utils/slowOperations.js'
  5. import {
  6. type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  7. logEvent,
  8. } from './analytics/index.js'
  9. /**
  10. * Get the current Kubernetes namespace:
  11. * Returns null on laptops/local development,
  12. * "default" for devboxes in default namespace,
  13. * "ts" for devboxes in ts namespace,
  14. * ...
  15. */
  16. const getKubernetesNamespace = memoize(async (): Promise<string | null> => {
  17. if (process.env.USER_TYPE !== 'ant') {
  18. return null
  19. }
  20. const namespacePath =
  21. '/var/run/secrets/kubernetes.io/serviceaccount/namespace'
  22. const namespaceNotFound = 'namespace not found'
  23. try {
  24. const content = await readFile(namespacePath, { encoding: 'utf8' })
  25. return content.trim()
  26. } catch {
  27. return namespaceNotFound
  28. }
  29. })
  30. /**
  31. * Get the OCI container ID from within a running container
  32. */
  33. export const getContainerId = memoize(async (): Promise<string | null> => {
  34. if (process.env.USER_TYPE !== 'ant') {
  35. return null
  36. }
  37. const containerIdPath = '/proc/self/mountinfo'
  38. const containerIdNotFound = 'container ID not found'
  39. const containerIdNotFoundInMountinfo = 'container ID not found in mountinfo'
  40. try {
  41. const mountinfo = (
  42. await readFile(containerIdPath, { encoding: 'utf8' })
  43. ).trim()
  44. // Pattern to match both Docker and containerd/CRI-O container IDs
  45. // Docker: /docker/containers/[64-char-hex]
  46. // Containerd: /sandboxes/[64-char-hex]
  47. const containerIdPattern =
  48. /(?:\/docker\/containers\/|\/sandboxes\/)([0-9a-f]{64})/
  49. const lines = mountinfo.split('\n')
  50. for (const line of lines) {
  51. const match = line.match(containerIdPattern)
  52. if (match && match[1]) {
  53. return match[1]
  54. }
  55. }
  56. return containerIdNotFoundInMountinfo
  57. } catch {
  58. return containerIdNotFound
  59. }
  60. })
  61. /**
  62. * Logs an event with the current namespace and tool permission context
  63. */
  64. export async function logPermissionContextForAnts(
  65. toolPermissionContext: ToolPermissionContext | null,
  66. moment: 'summary' | 'initialization',
  67. ): Promise<void> {
  68. if (process.env.USER_TYPE !== 'ant') {
  69. return
  70. }
  71. void logEvent('tengu_internal_record_permission_context', {
  72. moment:
  73. moment as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  74. namespace:
  75. (await getKubernetesNamespace()) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  76. toolPermissionContext: jsonStringify(
  77. toolPermissionContext,
  78. ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  79. containerId:
  80. (await getContainerId()) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  81. })
  82. }