@@ -18,9 +18,12 @@ module Mongo
1818 class Server
1919 class AppMetadata
2020 # Implements the logic from the handshake spec, for deducing and
21- # reporting the current FaaS environment in which the program is
21+ # reporting the current environment in which the program is
2222 # executing.
2323 #
24+ # This includes FaaS environment checks, as well as checks for the
25+ # presence of a container (Docker) and/or orchestrator (Kubernetes).
26+ #
2427 # @api private
2528 class Environment
2629 # Error class for reporting that too many discriminators were found
@@ -39,6 +42,10 @@ class TypeMismatch < Mongo::Error; end
3942 # Error class for reporting that the value for a field is too long.
4043 class ValueTooLong < Mongo ::Error ; end
4144
45+ # The name and location of the .dockerenv file that will signal the
46+ # presence of Docker.
47+ DOCKERENV_PATH = '/.dockerenv'
48+
4249 # This value is not explicitly specified in the spec, only implied to be
4350 # less than 512.
4451 MAXIMUM_VALUE_LENGTH = 500
@@ -102,9 +109,11 @@ class ValueTooLong < Mongo::Error; end
102109 # if the environment contains invalid or contradictory state, it will
103110 # be initialized with {{name}} set to {{nil}}.
104111 def initialize
112+ @fields = { }
105113 @error = nil
106114 @name = detect_environment
107- populate_fields
115+ populate_faas_fields
116+ detect_container
108117 rescue TooManyEnvironments => e
109118 self . error = "too many environments detected: #{ e . message } "
110119 rescue MissingVariable => e
@@ -115,6 +124,23 @@ def initialize
115124 self . error = "value for #{ e . message } is too long"
116125 end
117126
127+ # Queries the detected container information.
128+ #
129+ # @return [ Hash | nil ] the detected container information, or
130+ # nil if no container was detected.
131+ def container
132+ fields [ :container ]
133+ end
134+
135+ # Queries whether any environment information was able to be
136+ # detected.
137+ #
138+ # @return [ true | false ] if any environment information was
139+ # detected.
140+ def present?
141+ @name || fields . any?
142+ end
143+
118144 # Queries whether the current environment is a valid FaaS environment.
119145 #
120146 # @return [ true | false ] whether the environment is a FaaS
@@ -159,14 +185,11 @@ def vercel?
159185 @name == 'vercel'
160186 end
161187
162- # Compiles the detected environment information into a Hash. It will
163- # always include a {{name}} key, but may include other keys as well,
164- # depending on the detected FaaS environment. (See the handshake
165- # spec for details.)
188+ # Compiles the detected environment information into a Hash.
166189 #
167190 # @return [ Hash ] the detected environment information.
168191 def to_h
169- fields . merge ( name : name )
192+ name ? fields . merge ( name : name ) : fields
170193 end
171194
172195 private
@@ -192,6 +215,38 @@ def detect_environment
192215 names . first
193216 end
194217
218+ # Looks for the presence of a container. Currently can detect
219+ # Docker (by the existence of a .dockerenv file in the root
220+ # directory) and Kubernetes (by the existence of the KUBERNETES_SERVICE_HOST
221+ # environment variable).
222+ def detect_container
223+ runtime = docker_present? && 'docker'
224+ orchestrator = kubernetes_present? && 'kubernetes'
225+
226+ return unless runtime || orchestrator
227+
228+ fields [ :container ] = { }
229+ fields [ :container ] [ :runtime ] = runtime if runtime
230+ fields [ :container ] [ :orchestrator ] = orchestrator if orchestrator
231+ end
232+
233+ # Checks for the existence of a .dockerenv in the root directory.
234+ def docker_present?
235+ File . exist? ( dockerenv_path )
236+ end
237+
238+ # Implementing this as a method so that it can be mocked in tests, to
239+ # test the presence or absence of Docker.
240+ def dockerenv_path
241+ DOCKERENV_PATH
242+ end
243+
244+ # Checks for the presence of a non-empty KUBERNETES_SERVICE_HOST
245+ # environment variable.
246+ def kubernetes_present?
247+ !ENV [ 'KUBERNETES_SERVICE_HOST' ] . to_s . empty?
248+ end
249+
195250 # Determines whether the named environment variable exists, and (if
196251 # a pattern has been declared for that descriminator) whether the
197252 # pattern matches the value of the variable.
@@ -212,10 +267,10 @@ def discriminator_matches?(var)
212267 # Extracts environment information from the current environment
213268 # variables, based on the detected FaaS environment. Populates the
214269 # {{@fields}} instance variable.
215- def populate_fields
270+ def populate_faas_fields
216271 return unless name
217272
218- @fields = FIELDS [ name ] . each_with_object ( { } ) do |( var , defn ) , fields |
273+ FIELDS [ name ] . each_with_object ( @fields ) do |( var , defn ) , fields |
219274 fields [ defn [ :field ] ] = extract_field ( var , defn )
220275 end
221276 end
0 commit comments