1use crate::regex_engine::Regex;
5#[cfg(target_os = "linux")]
6use std::collections::HashMap;
7use std::sync::LazyLock;
8
9const WEBSITE_OWNER_NAME: &str = "WEBSITE_OWNER_NAME";
10const WEBSITE_SITE_NAME: &str = "WEBSITE_SITE_NAME";
11const WEBSITE_RESOURCE_GROUP: &str = "WEBSITE_RESOURCE_GROUP";
12const SITE_EXTENSION_VERSION: &str = "DD_AAS_DOTNET_EXTENSION_VERSION";
13const WEBSITE_OS: &str = "WEBSITE_OS";
14const COMPUTERNAME: &str = "COMPUTERNAME";
15const CONTAINER_NAME: &str = "CONTAINER_NAME";
16const WEBSITE_POD_NAME: &str = "WEBSITE_POD_NAME";
17const INSTANCE_ID: &str = "WEBSITE_INSTANCE_ID";
18const SERVICE_CONTEXT: &str = "DD_AZURE_APP_SERVICES";
19const FUNCTIONS_WORKER_RUNTIME: &str = "FUNCTIONS_WORKER_RUNTIME";
20const FUNCTIONS_WORKER_RUNTIME_VERSION: &str = "FUNCTIONS_WORKER_RUNTIME_VERSION";
21const FUNCTIONS_EXTENSION_VERSION: &str = "FUNCTIONS_EXTENSION_VERSION";
22const DD_AZURE_RESOURCE_GROUP: &str = "DD_AZURE_RESOURCE_GROUP";
23pub const WEBSITE_SKU: &str = "WEBSITE_SKU";
24pub const REGION_NAME: &str = "REGION_NAME";
25
26pub const UNKNOWN_VALUE: &str = "unknown";
27
28enum AzureContext {
29 AzureFunctions,
30 AzureAppService,
31}
32
33#[cfg(target_os = "linux")]
57static PROC_ENVIRON: LazyLock<HashMap<String, String>> = LazyLock::new(read_proc_self_environ);
58
59#[cfg(target_os = "linux")]
63const AAS_VAR_NAMES: &[&str] = &[
64 WEBSITE_OWNER_NAME,
65 WEBSITE_SITE_NAME,
66 WEBSITE_RESOURCE_GROUP,
67 SITE_EXTENSION_VERSION,
68 WEBSITE_OS,
69 COMPUTERNAME,
70 CONTAINER_NAME,
71 WEBSITE_POD_NAME,
72 INSTANCE_ID,
73 SERVICE_CONTEXT,
74 FUNCTIONS_WORKER_RUNTIME,
75 FUNCTIONS_WORKER_RUNTIME_VERSION,
76 FUNCTIONS_EXTENSION_VERSION,
77 DD_AZURE_RESOURCE_GROUP,
78 WEBSITE_SKU,
79];
80
81#[cfg(target_os = "linux")]
82fn read_proc_self_environ() -> HashMap<String, String> {
83 let bytes = match std::fs::read("/proc/self/environ") {
84 Ok(b) => b,
85 Err(_) => return HashMap::new(),
86 };
87 let mut map = HashMap::new();
88 for entry in bytes.split(|&b| b == 0) {
89 if entry.is_empty() {
90 continue;
91 }
92 let s = match core::str::from_utf8(entry) {
93 Ok(s) => s,
94 Err(_) => continue,
95 };
96 if let Some(eq) = s.find('=') {
97 let key = &s[..eq];
98 if AAS_VAR_NAMES.contains(&key) {
99 map.insert(key.to_string(), s[eq + 1..].to_string());
100 }
101 }
102 }
103 map
104}
105
106fn read_aas_var(name: &str) -> Option<String> {
107 #[cfg(target_os = "linux")]
108 {
109 PROC_ENVIRON.get(name).cloned()
110 }
111 #[cfg(not(target_os = "linux"))]
112 {
113 std::env::var(name).ok()
114 }
115}
116
117macro_rules! get_trimmed_env_var {
118 ($name:expr) => {
119 crate::azure_app_services::read_aas_var($name)
120 .map(|v| v.trim().to_string())
121 .filter(|s| !s.is_empty())
122 };
123}
124
125macro_rules! get_value_or_unknown {
126 ($name:expr) => {
127 $name.as_ref().map(|s| s.as_str()).unwrap_or(UNKNOWN_VALUE)
128 };
129}
130
131trait ToBoolean {
132 fn to_bool(&self) -> bool;
133}
134
135impl ToBoolean for String {
136 fn to_bool(&self) -> bool {
137 matches!(
138 self.to_lowercase().as_str(),
139 "true" | "t" | "y" | "1" | "yes"
140 )
141 }
142}
143
144pub trait QueryEnv {
145 fn get_var(&self, var: &str) -> Option<String>;
146}
147
148struct RealEnv;
149
150impl QueryEnv for RealEnv {
151 fn get_var(&self, var: &str) -> Option<String> {
152 get_trimmed_env_var!(var)
153 }
154}
155
156#[derive(Default)]
157pub struct AzureMetadata {
158 resource_id: Option<String>,
159 subscription_id: Option<String>,
160 site_name: Option<String>,
161 resource_group: Option<String>,
162 extension_version: Option<String>,
163 operating_system: String,
164 instance_name: Option<String>,
165 instance_id: Option<String>,
166 site_kind: String,
167 site_type: String,
168 runtime: Option<String>,
169 runtime_version: Option<String>,
170 function_runtime_version: Option<String>,
171}
172
173impl AzureMetadata {
174 fn get_azure_context<T: QueryEnv>(query: &T) -> AzureContext {
175 match (
176 query.get_var(FUNCTIONS_WORKER_RUNTIME),
177 query.get_var(FUNCTIONS_EXTENSION_VERSION),
178 ) {
179 (Some(_), Some(_)) => AzureContext::AzureFunctions,
180 (Some(_), None) => AzureContext::AzureFunctions,
181 (None, Some(_)) => AzureContext::AzureFunctions,
182 (None, None) => AzureContext::AzureAppService,
183 }
184 }
185
186 fn extract_subscription_id(s: Option<String>) -> Option<String> {
187 s?.split('+')
188 .next()
189 .filter(|s| !s.trim().is_empty())
190 .map(|v| v.to_string())
191 }
192
193 fn extract_resource_group(s: Option<String>) -> Option<String> {
194 #[allow(clippy::unwrap_used)]
195 let re: Regex = Regex::new(r".+\+(.+)-.+webspace(-Linux)?").unwrap();
196
197 s.as_ref().and_then(|text| {
198 re.captures(text)
199 .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string()))
200 })
201 }
202
203 fn build_resource_id(
208 subscription_id: Option<&String>,
209 site_name: Option<&String>,
210 resource_group: Option<&String>,
211 ) -> Option<String> {
212 match (subscription_id, site_name, resource_group) {
213 (Some(id_sub), Some(sitename), Some(res_grp)) => Some(
214 format!("/subscriptions/{id_sub}/resourcegroups/{res_grp}/providers/microsoft.web/sites/{sitename}")
215 .to_lowercase(),
216 ),
217 _ => None,
218 }
219 }
220
221 fn build_metadata<T: QueryEnv>(query: T) -> Option<Self> {
222 let subscription_id =
223 AzureMetadata::extract_subscription_id(query.get_var(WEBSITE_OWNER_NAME));
224 let site_name = query.get_var(WEBSITE_SITE_NAME);
225
226 let (site_kind, site_type) = match AzureMetadata::get_azure_context(&query) {
227 AzureContext::AzureFunctions => ("functionapp".to_owned(), "function".to_owned()),
228 _ => ("app".to_owned(), "app".to_owned()),
229 };
230
231 let website_sku = query.get_var(WEBSITE_SKU);
232
233 let resource_group = query
234 .get_var(DD_AZURE_RESOURCE_GROUP)
235 .or_else(|| query.get_var(WEBSITE_RESOURCE_GROUP))
236 .or_else(|| {
237 match website_sku.as_deref() {
239 Some("FlexConsumption") => None,
240 _ => AzureMetadata::extract_resource_group(query.get_var(WEBSITE_OWNER_NAME)),
245 }
246 });
247
248 let resource_id = AzureMetadata::build_resource_id(
249 subscription_id.as_ref(),
250 site_name.as_ref(),
251 resource_group.as_ref(),
252 );
253 let extension_version = query.get_var(SITE_EXTENSION_VERSION);
254 let operating_system = query
255 .get_var(WEBSITE_OS)
256 .unwrap_or(std::env::consts::OS.to_string());
257
258 let computer_name = query.get_var(COMPUTERNAME);
259 let pod_name = query.get_var(WEBSITE_POD_NAME);
260 let container_name = query.get_var(CONTAINER_NAME);
261 let instance_name = resolve_instance_name(
262 computer_name.as_deref(),
263 pod_name.as_deref(),
264 container_name.as_deref(),
265 );
266
267 let instance_id = query.get_var(INSTANCE_ID);
268
269 let runtime = query.get_var(FUNCTIONS_WORKER_RUNTIME);
270 let runtime_version = query.get_var(FUNCTIONS_WORKER_RUNTIME_VERSION);
271 let function_runtime_version = query.get_var(FUNCTIONS_EXTENSION_VERSION);
272
273 Some(AzureMetadata {
274 resource_id,
275 subscription_id,
276 site_name,
277 resource_group,
278 extension_version,
279 operating_system,
280 instance_name,
281 instance_id,
282 site_kind,
283 site_type,
284 runtime,
285 runtime_version,
286 function_runtime_version,
287 })
288 }
289
290 pub fn new<T: QueryEnv>(query: T) -> Option<Self> {
291 let is_relevant = query
292 .get_var(SERVICE_CONTEXT)
293 .map(|s| s.to_bool())
294 .unwrap_or(false);
295
296 if !is_relevant {
297 return None;
298 }
299
300 AzureMetadata::build_metadata(query)
301 }
302
303 pub fn new_function<T: QueryEnv>(query: T) -> Option<Self> {
304 match matches!(
305 AzureMetadata::get_azure_context(&query),
306 AzureContext::AzureFunctions
307 ) {
308 true => AzureMetadata::build_metadata(query),
309 false => None,
310 }
311 }
312
313 pub fn get_resource_id(&self) -> &str {
314 get_value_or_unknown!(self.resource_id)
315 }
316
317 pub fn get_subscription_id(&self) -> &str {
318 get_value_or_unknown!(self.subscription_id)
319 }
320
321 pub fn get_site_name(&self) -> &str {
322 get_value_or_unknown!(self.site_name)
323 }
324
325 pub fn get_resource_group(&self) -> &str {
326 get_value_or_unknown!(self.resource_group)
327 }
328
329 pub fn get_extension_version(&self) -> &str {
330 get_value_or_unknown!(self.extension_version)
331 }
332
333 pub fn get_operating_system(&self) -> &str {
334 self.operating_system.as_str()
335 }
336
337 pub fn get_instance_name(&self) -> &str {
338 get_value_or_unknown!(self.instance_name)
339 }
340
341 pub fn get_instance_id(&self) -> &str {
342 get_value_or_unknown!(self.instance_id)
343 }
344
345 pub fn get_site_type(&self) -> &str {
346 self.site_type.as_str()
347 }
348
349 pub fn get_site_kind(&self) -> &str {
350 self.site_kind.as_str()
351 }
352
353 pub fn get_runtime(&self) -> &str {
354 get_value_or_unknown!(self.runtime)
355 }
356
357 pub fn get_runtime_version(&self) -> &str {
358 get_value_or_unknown!(self.runtime_version)
359 }
360
361 pub fn get_function_runtime_version(&self) -> &str {
362 get_value_or_unknown!(self.function_runtime_version)
363 }
364
365 pub fn get_app_service_tags(&self) -> impl ExactSizeIterator<Item = (&'static str, &str)> {
368 [
369 (
370 "aas.environment.extension_version",
371 self.get_extension_version(),
372 ),
373 ("aas.environment.instance_id", self.get_instance_id()),
374 ("aas.environment.instance_name", self.get_instance_name()),
375 ("aas.environment.os", self.get_operating_system()),
376 ("aas.resource.group", self.get_resource_group()),
377 ("aas.resource.id", self.get_resource_id()),
378 ("aas.site.kind", self.get_site_kind()),
379 ("aas.site.name", self.get_site_name()),
380 ("aas.site.type", self.get_site_type()),
381 ("aas.subscription.id", self.get_subscription_id()),
382 ]
383 .into_iter()
384 }
385
386 pub fn get_function_tags(&self) -> impl ExactSizeIterator<Item = (&'static str, &str)> {
389 [
390 ("aas.environment.instance_id", self.get_instance_id()),
391 ("aas.environment.instance_name", self.get_instance_name()),
392 ("aas.environment.os", self.get_operating_system()),
393 ("aas.environment.runtime", self.get_runtime()),
394 (
395 "aas.environment.runtime_version",
396 self.get_runtime_version(),
397 ),
398 (
399 "aas.environment.function_runtime",
400 self.get_function_runtime_version(),
401 ),
402 ("aas.resource.group", self.get_resource_group()),
403 ("aas.resource.id", self.get_resource_id()),
404 ("aas.site.kind", self.get_site_kind()),
405 ("aas.site.name", self.get_site_name()),
406 ("aas.site.type", self.get_site_type()),
407 ("aas.subscription.id", self.get_subscription_id()),
408 ]
409 .into_iter()
410 }
411}
412
413fn resolve_instance_name(
415 computer_name: Option<&str>,
416 pod_name: Option<&str>,
417 container_name: Option<&str>,
418) -> Option<String> {
419 fn non_empty(s: Option<&str>) -> Option<&str> {
420 s.map(|v| v.trim()).filter(|v| !v.is_empty())
421 }
422
423 non_empty(computer_name)
424 .or_else(|| non_empty(pod_name))
425 .or_else(|| non_empty(container_name))
426 .map(|s| s.to_string())
427}
428
429pub static AAS_METADATA: LazyLock<Option<AzureMetadata>> =
430 LazyLock::new(|| AzureMetadata::new(RealEnv {}));
431
432pub static AAS_METADATA_FUNCTION: LazyLock<Option<AzureMetadata>> =
433 LazyLock::new(|| AzureMetadata::new_function(RealEnv {}));
434
435#[cfg(test)]
436mod tests {
437
438 use indexmap::IndexMap;
439
440 use crate::azure_app_services::{QueryEnv, WEBSITE_OWNER_NAME};
441
442 use super::*;
443
444 struct MockEnv {
445 pub env_vars: IndexMap<String, String>,
446 }
447
448 impl MockEnv {
449 pub fn new(vars: &[(&str, &str)]) -> Self {
450 let mut env_vars: IndexMap<String, String> = IndexMap::new();
451 vars.iter().for_each(|(name, value)| {
452 env_vars.insert(name.to_string(), value.to_string());
453 });
454
455 MockEnv { env_vars }
456 }
457 }
458
459 impl QueryEnv for MockEnv {
460 fn get_var(&self, var: &str) -> Option<String> {
461 self.env_vars.get(var).cloned()
462 }
463 }
464
465 #[test]
466 fn test_metadata_is_not_relevant_by_default() {
467 let mocked_env = MockEnv::new(&[]);
468
469 let metadata = AzureMetadata::new(mocked_env);
470 assert!(metadata.is_none());
471 }
472
473 #[test]
474 fn test_metadata_is_relevant_first() {
475 let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "true")]);
476
477 let metadata = AzureMetadata::new(mocked_env);
478 assert!(metadata.is_some());
479 }
480
481 #[test]
482 fn test_metadata_is_relevant_second() {
483 let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "t")]);
484
485 let metadata = AzureMetadata::new(mocked_env);
486 assert!(metadata.is_some());
487 }
488
489 #[test]
490 fn test_metadata_is_relevant_third() {
491 let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "TrUe")]);
492
493 let metadata = AzureMetadata::new(mocked_env);
494 assert!(metadata.is_some());
495 }
496
497 #[test]
498 fn test_metadata_is_relevant_fourth() {
499 let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "1")]);
500
501 let metadata = AzureMetadata::new(mocked_env);
502 assert!(metadata.is_some());
503 }
504
505 #[test]
506 fn test_metadata_is_relevant_fifth() {
507 let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "yEs")]);
508
509 let metadata = AzureMetadata::new(mocked_env);
510 assert!(metadata.is_some());
511 }
512
513 #[test]
514 fn test_metadata_is_relevant_sixth() {
515 let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "Y")]);
516
517 let metadata = AzureMetadata::new(mocked_env);
518 assert!(metadata.is_some());
519 }
520
521 #[test]
522 fn test_metadata_is_not_relevant_if_explicit() {
523 let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "0")]);
524
525 let metadata = AzureMetadata::new(mocked_env);
526 assert!(metadata.is_none());
527 }
528
529 #[test]
530 fn test_extract_subscription_without_plus_sign() {
531 let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, "foo"), (SERVICE_CONTEXT, "1")]);
532
533 let metadata = AzureMetadata::new(mocked_env).unwrap();
534
535 let expected_id = "foo";
536
537 assert_eq!(metadata.get_subscription_id(), expected_id);
538 }
539
540 #[test]
541 fn test_extract_subscription_with_plus_sign() {
542 let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, "foo+bar"), (SERVICE_CONTEXT, "1")]);
543
544 let metadata = AzureMetadata::new(mocked_env).unwrap();
545
546 let expected_id = "foo";
547 assert_eq!(metadata.get_subscription_id(), expected_id);
548 }
549
550 #[test]
551 fn test_extract_subscription_with_empty_string() {
552 let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, ""), (SERVICE_CONTEXT, "1")]);
553
554 let metadata = AzureMetadata::new(mocked_env).unwrap();
555
556 assert_eq!(metadata.get_subscription_id(), UNKNOWN_VALUE);
557 }
558
559 #[test]
560 fn test_extract_subscription_with_only_whitespaces() {
561 let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, " "), (SERVICE_CONTEXT, "1")]);
562
563 let metadata = AzureMetadata::new(mocked_env).unwrap();
564
565 assert_eq!(metadata.get_subscription_id(), UNKNOWN_VALUE);
566 }
567
568 #[test]
569 fn test_extract_subscription_with_only_plus_sign() {
570 let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, "+"), (SERVICE_CONTEXT, "1")]);
571
572 let metadata = AzureMetadata::new(mocked_env).unwrap();
573
574 assert_eq!(metadata.get_subscription_id(), UNKNOWN_VALUE);
575 }
576
577 #[test]
578 fn test_extract_subscription_with_whitespaces_separated_by_plus() {
579 let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, " + "), (SERVICE_CONTEXT, "1")]);
580
581 let metadata = AzureMetadata::new(mocked_env).unwrap();
582
583 assert_eq!(metadata.get_subscription_id(), UNKNOWN_VALUE);
584 }
585
586 #[test]
587 fn test_extract_subscription_plus_sign_and_other_string() {
588 let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, "+other"), (SERVICE_CONTEXT, "1")]);
589
590 let metadata = AzureMetadata::new(mocked_env).unwrap();
591
592 assert_eq!(metadata.get_subscription_id(), UNKNOWN_VALUE);
593 }
594
595 #[test]
596 fn test_extract_resource_group_pattern_match_linux() {
597 let mocked_env = MockEnv::new(&[
598 (
599 WEBSITE_OWNER_NAME,
600 "00000000-0000-0000-0000-000000000000+test-rg-EastUSwebspace-Linux",
601 ),
602 ("FUNCTIONS_WORKER_RUNTIME", "node"),
603 ("FUNCTIONS_EXTENSION_VERSION", "~4"),
604 ]);
605
606 let metadata = AzureMetadata::new_function(mocked_env).unwrap();
607
608 let expected_resource_group = "test-rg";
609
610 assert_eq!(metadata.get_resource_group(), expected_resource_group);
611 }
612
613 #[test]
614 fn test_extract_resource_group_pattern_match_windows() {
615 let mocked_env = MockEnv::new(&[
616 (
617 WEBSITE_OWNER_NAME,
618 "00000000-0000-0000-0000-000000000000+test-rg-EastUSwebspace",
619 ),
620 ("FUNCTIONS_WORKER_RUNTIME", "node"),
621 ("FUNCTIONS_EXTENSION_VERSION", "~4"),
622 ]);
623
624 let metadata = AzureMetadata::new_function(mocked_env).unwrap();
625
626 let expected_resource_group = "test-rg";
627
628 assert_eq!(metadata.get_resource_group(), expected_resource_group);
629 }
630
631 #[test]
632 fn test_extract_resource_group_no_pattern_match() {
633 let mocked_env = MockEnv::new(&[
634 (WEBSITE_OWNER_NAME, "foo"),
635 (FUNCTIONS_WORKER_RUNTIME, "node"),
636 (FUNCTIONS_EXTENSION_VERSION, "~4"),
637 ]);
638
639 let metadata = AzureMetadata::new_function(mocked_env).unwrap();
640
641 assert_eq!(metadata.get_resource_group(), UNKNOWN_VALUE);
642 }
643
644 #[test]
645 fn test_use_resource_group_from_env_var_if_available() {
646 let mocked_env = MockEnv::new(&[
647 (WEBSITE_RESOURCE_GROUP, "test-rg-env-var"),
648 (
649 WEBSITE_OWNER_NAME,
650 "00000000-0000-0000-0000-000000000000+test-rg-EastUSwebspace-Linux",
651 ),
652 (SERVICE_CONTEXT, "1"),
653 (WEBSITE_SKU, "ElasticPremium"),
654 ]);
655
656 let metadata = AzureMetadata::new(mocked_env).unwrap();
657
658 let expected_resource_group = "test-rg-env-var";
659
660 assert_eq!(metadata.get_resource_group(), expected_resource_group);
661 }
662
663 #[test]
664 fn test_flex_consumption_resource_group_is_none_without_dd_azure_resource_group() {
665 let mocked_env = MockEnv::new(&[
666 (
667 WEBSITE_OWNER_NAME,
668 "00000000-0000-0000-0000-000000000000+flex-EastUSwebspace-Linux",
669 ),
670 (WEBSITE_SKU, "FlexConsumption"),
671 (FUNCTIONS_WORKER_RUNTIME, "node"),
672 ]);
673
674 let metadata = AzureMetadata::new_function(mocked_env).unwrap();
675
676 assert_eq!(metadata.get_resource_group(), UNKNOWN_VALUE);
677 }
678
679 #[test]
680 fn test_flex_consumption_uses_dd_azure_resource_group() {
681 let mocked_env = MockEnv::new(&[
682 (
683 WEBSITE_OWNER_NAME,
684 "00000000-0000-0000-0000-000000000000+flex-EastUSwebspace-Linux",
685 ),
686 (DD_AZURE_RESOURCE_GROUP, "test-flex-rg"),
687 (WEBSITE_SKU, "FlexConsumption"),
688 (FUNCTIONS_WORKER_RUNTIME, "node"),
689 ]);
690
691 let metadata = AzureMetadata::new_function(mocked_env).unwrap();
692
693 assert_eq!(metadata.get_resource_group(), "test-flex-rg");
696 }
697
698 #[test]
699 fn test_dd_azure_resource_group_has_highest_priority() {
700 let mocked_env = MockEnv::new(&[
701 (WEBSITE_RESOURCE_GROUP, "test-rg-env-var"),
702 (
703 WEBSITE_OWNER_NAME,
704 "00000000-0000-0000-0000-000000000000+test-rg-EastUSwebspace-Linux",
705 ),
706 (DD_AZURE_RESOURCE_GROUP, "dd-azure-rg-override"),
707 (SERVICE_CONTEXT, "1"),
708 ]);
709
710 let metadata = AzureMetadata::new(mocked_env).unwrap();
711
712 let expected_resource_group = "dd-azure-rg-override";
715
716 assert_eq!(metadata.get_resource_group(), expected_resource_group);
717 }
718
719 #[test]
720 fn test_build_resource_id() {
721 let mocked_env = MockEnv::new(&[
722 (WEBSITE_OWNER_NAME, "foo"),
723 (WEBSITE_SITE_NAME, "my_website"),
724 (WEBSITE_RESOURCE_GROUP, "resource_group"),
725 (SERVICE_CONTEXT, "1"),
726 ]);
727
728 let metadata = AzureMetadata::new(mocked_env).unwrap();
729
730 assert_eq!(
731 metadata.get_resource_id(),
732 "/subscriptions/foo/resourcegroups/resource_group/providers/microsoft.web/sites/my_website"
733 )
734 }
735
736 #[test]
737 fn test_build_resource_id_with_missing_subscription_id() {
738 let mocked_env = MockEnv::new(&[
739 (WEBSITE_SITE_NAME, "my_website"),
740 (WEBSITE_RESOURCE_GROUP, "resource_group"),
741 (SERVICE_CONTEXT, "1"),
742 ]);
743
744 let metadata = AzureMetadata::new(mocked_env).unwrap();
745
746 assert_eq!(metadata.get_resource_id(), UNKNOWN_VALUE)
747 }
748
749 #[test]
750 fn test_build_resource_id_with_missing_site_name() {
751 let mocked_env = MockEnv::new(&[
752 (WEBSITE_OWNER_NAME, "foo"),
753 (WEBSITE_RESOURCE_GROUP, "resource_group"),
754 (SERVICE_CONTEXT, "1"),
755 ]);
756
757 let metadata = AzureMetadata::new(mocked_env).unwrap();
758
759 assert_eq!(metadata.get_resource_id(), UNKNOWN_VALUE)
760 }
761
762 #[test]
763 fn test_build_resource_id_with_missing_resource_group() {
764 let mocked_env = MockEnv::new(&[
765 (WEBSITE_OWNER_NAME, "foo"),
766 (WEBSITE_SITE_NAME, "my_website"),
767 (SERVICE_CONTEXT, "1"),
768 ]);
769
770 let metadata = AzureMetadata::new(mocked_env).unwrap();
771
772 assert_eq!(metadata.get_resource_id(), UNKNOWN_VALUE)
773 }
774
775 #[test]
776 fn test_build_resource_id_with_missing_info() {
777 let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "1")]);
778 let metadata = AzureMetadata::new(mocked_env).unwrap();
779
780 assert_eq!(metadata.get_resource_id(), UNKNOWN_VALUE)
781 }
782
783 #[test]
784 fn test_site_type_and_kind_default() {
785 let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "1")]);
786 let metadata = AzureMetadata::new(mocked_env).unwrap();
787
788 assert_eq!(metadata.get_site_type(), "app");
789 assert_eq!(metadata.get_site_kind(), "app")
790 }
791
792 #[test]
793 fn test_site_type_and_kind_if_worker_runtime_not_specified() {
794 let mocked_env = MockEnv::new(&[
795 (FUNCTIONS_WORKER_RUNTIME, "my_runtime"),
796 (SERVICE_CONTEXT, "1"),
797 ]);
798 let metadata = AzureMetadata::new(mocked_env).unwrap();
799
800 assert_eq!(metadata.get_site_kind(), "functionapp");
801 assert_eq!(metadata.get_site_type(), "function")
802 }
803
804 #[test]
805 fn test_site_type_and_kind_if_extension_version_not_specified() {
806 let mocked_env = MockEnv::new(&[
807 (FUNCTIONS_EXTENSION_VERSION, "next_version"),
808 (SERVICE_CONTEXT, "1"),
809 ]);
810 let metadata = AzureMetadata::new(mocked_env).unwrap();
811
812 assert_eq!(metadata.get_site_kind(), "functionapp");
813 assert_eq!(metadata.get_site_type(), "function")
814 }
815
816 #[test]
817 fn test_site_type_and_kind_if_both_specified() {
818 let mocked_env = MockEnv::new(&[
819 (FUNCTIONS_WORKER_RUNTIME, "my_runtime"),
820 (FUNCTIONS_EXTENSION_VERSION, "next_version"),
821 (SERVICE_CONTEXT, "1"),
822 ]);
823 let metadata = AzureMetadata::new(mocked_env).unwrap();
824
825 assert_eq!(metadata.get_site_kind(), "functionapp");
826 assert_eq!(metadata.get_site_type(), "function")
827 }
828
829 #[test]
830 fn test_check_other_simple_env_retrieval() {
831 let expected_site_name = "my_site_name".to_owned();
832 let expected_resource_group = "my_resource_group".to_owned();
833 let expected_site_version = "v42".to_owned();
834 let expected_operating_system = "FreeBSD".to_owned();
835 let expected_instance_name = "my_instance_name".to_owned();
836 let expected_instance_id = "my_instance_id".to_owned();
837 let expected_function_extension_version = "~4".to_owned();
838 let expected_runtime = "node".to_owned();
839 let expected_runtime_version = "18".to_owned();
840
841 let mocked_env = MockEnv::new(&[
842 (WEBSITE_SITE_NAME, expected_site_name.as_str()),
843 (WEBSITE_RESOURCE_GROUP, expected_resource_group.as_str()),
844 (SITE_EXTENSION_VERSION, expected_site_version.as_str()),
845 (WEBSITE_OS, expected_operating_system.as_str()),
846 (COMPUTERNAME, expected_instance_name.as_str()),
847 (INSTANCE_ID, expected_instance_id.as_str()),
848 (SERVICE_CONTEXT, "1"),
849 (
850 FUNCTIONS_EXTENSION_VERSION,
851 expected_function_extension_version.as_str(),
852 ),
853 (FUNCTIONS_WORKER_RUNTIME, expected_runtime.as_str()),
854 (
855 FUNCTIONS_WORKER_RUNTIME_VERSION,
856 expected_runtime_version.as_str(),
857 ),
858 ]);
859
860 let metadata = AzureMetadata::new(mocked_env).unwrap();
861
862 assert_eq!(expected_site_name, metadata.get_site_name());
863 assert_eq!(expected_resource_group, metadata.get_resource_group());
864 assert_eq!(expected_site_version, metadata.get_extension_version());
865 assert_eq!(expected_operating_system, metadata.get_operating_system());
866 assert_eq!(expected_instance_name, metadata.get_instance_name());
867 assert_eq!(expected_instance_id, metadata.get_instance_id());
868 assert_eq!(
869 expected_function_extension_version,
870 metadata.get_function_runtime_version()
871 );
872 assert_eq!(expected_runtime, metadata.get_runtime());
873 assert_eq!(expected_runtime_version, metadata.get_runtime_version());
874 }
875
876 #[test]
877 fn test_get_app_service_tags() {
878 let expected_site_name = "my_site_name";
879 let expected_resource_group = "my_resource_group";
880 let expected_site_version = "v42";
881 let expected_operating_system = "FreeBSD";
882 let expected_instance_name = "my_instance_name";
883 let expected_instance_id = "my_instance_id";
884 let expected_subscription_id = "sub-123";
885 let expected_resource_id = "/subscriptions/sub-123/resourcegroups/my_resource_group/providers/microsoft.web/sites/my_site_name";
886
887 let mocked_env = MockEnv::new(&[
888 (WEBSITE_SITE_NAME, expected_site_name),
889 (WEBSITE_RESOURCE_GROUP, expected_resource_group),
890 (SITE_EXTENSION_VERSION, expected_site_version),
891 (WEBSITE_OS, expected_operating_system),
892 (COMPUTERNAME, expected_instance_name),
893 (INSTANCE_ID, expected_instance_id),
894 (SERVICE_CONTEXT, "1"),
895 (
896 WEBSITE_OWNER_NAME,
897 &format!("{}+rg-webspace", expected_subscription_id),
898 ),
899 ]);
900
901 let metadata = AzureMetadata::new(mocked_env).unwrap();
902
903 let tags: std::collections::HashMap<&str, &str> = metadata.get_app_service_tags().collect();
905
906 assert_eq!(tags.len(), 10);
908 assert_eq!(tags.get("aas.resource.id"), Some(&expected_resource_id));
909 assert_eq!(
910 tags.get("aas.environment.extension_version"),
911 Some(&expected_site_version)
912 );
913 assert_eq!(
914 tags.get("aas.environment.instance_id"),
915 Some(&expected_instance_id)
916 );
917 assert_eq!(
918 tags.get("aas.environment.instance_name"),
919 Some(&expected_instance_name)
920 );
921 assert_eq!(
922 tags.get("aas.environment.os"),
923 Some(&expected_operating_system)
924 );
925 assert_eq!(
926 tags.get("aas.resource.group"),
927 Some(&expected_resource_group)
928 );
929 assert_eq!(tags.get("aas.site.name"), Some(&expected_site_name));
930 assert_eq!(tags.get("aas.site.kind"), Some(&"app"));
931 assert_eq!(tags.get("aas.site.type"), Some(&"app"));
932 assert_eq!(
933 tags.get("aas.subscription.id"),
934 Some(&expected_subscription_id)
935 );
936
937 assert_eq!(tags.get("aas.environment.runtime"), None);
939 assert_eq!(tags.get("aas.environment.runtime_version"), None);
940 assert_eq!(tags.get("aas.environment.function_runtime"), None);
941
942 let iter = metadata.get_app_service_tags();
944 assert_eq!(iter.len(), 10);
945 }
946
947 #[test]
948 fn test_get_function_tags() {
949 let expected_site_name = "my_site_name";
950 let expected_resource_group = "my_resource_group";
951 let expected_operating_system = "FreeBSD";
952 let expected_instance_name = "my_instance_name";
953 let expected_instance_id = "my_instance_id";
954 let expected_function_extension_version = "~4";
955 let expected_runtime = "node";
956 let expected_runtime_version = "18";
957 let expected_subscription_id = "sub-123";
958 let expected_resource_id = "/subscriptions/sub-123/resourcegroups/my_resource_group/providers/microsoft.web/sites/my_site_name";
959
960 let mocked_env = MockEnv::new(&[
961 (WEBSITE_SITE_NAME, expected_site_name),
962 (WEBSITE_RESOURCE_GROUP, expected_resource_group),
963 (WEBSITE_OS, expected_operating_system),
964 (COMPUTERNAME, expected_instance_name),
965 (INSTANCE_ID, expected_instance_id),
966 (SERVICE_CONTEXT, "1"),
967 (
968 FUNCTIONS_EXTENSION_VERSION,
969 expected_function_extension_version,
970 ),
971 (FUNCTIONS_WORKER_RUNTIME, expected_runtime),
972 (FUNCTIONS_WORKER_RUNTIME_VERSION, expected_runtime_version),
973 (
974 WEBSITE_OWNER_NAME,
975 &format!("{}+rg-webspace", expected_subscription_id),
976 ),
977 ]);
978
979 let metadata = AzureMetadata::new(mocked_env).unwrap();
980
981 let tags: std::collections::HashMap<&str, &str> = metadata.get_function_tags().collect();
983
984 assert_eq!(tags.len(), 12);
986 assert_eq!(tags.get("aas.resource.id"), Some(&expected_resource_id));
987 assert_eq!(
988 tags.get("aas.environment.instance_id"),
989 Some(&expected_instance_id)
990 );
991 assert_eq!(
992 tags.get("aas.environment.instance_name"),
993 Some(&expected_instance_name)
994 );
995 assert_eq!(
996 tags.get("aas.environment.os"),
997 Some(&expected_operating_system)
998 );
999 assert_eq!(tags.get("aas.environment.runtime"), Some(&expected_runtime));
1000 assert_eq!(
1001 tags.get("aas.environment.runtime_version"),
1002 Some(&expected_runtime_version)
1003 );
1004 assert_eq!(
1005 tags.get("aas.environment.function_runtime"),
1006 Some(&expected_function_extension_version)
1007 );
1008 assert_eq!(
1009 tags.get("aas.resource.group"),
1010 Some(&expected_resource_group)
1011 );
1012 assert_eq!(tags.get("aas.site.name"), Some(&expected_site_name));
1013 assert_eq!(tags.get("aas.site.kind"), Some(&"functionapp"));
1014 assert_eq!(tags.get("aas.site.type"), Some(&"function"));
1015 assert_eq!(
1016 tags.get("aas.subscription.id"),
1017 Some(&expected_subscription_id)
1018 );
1019
1020 assert_eq!(tags.get("aas.environment.extension_version"), None);
1022
1023 let iter = metadata.get_function_tags();
1025 assert_eq!(iter.len(), 12);
1026 }
1027
1028 #[test]
1029 fn test_get_trimmed_env_var_missing() {
1030 assert_eq!(
1033 get_trimmed_env_var!("__LIBDD_AAS_DEFINITELY_NOT_SET__"),
1034 None
1035 );
1036 }
1037
1038 #[test]
1039 fn test_instance_name_computer_name_wins_over_pod_and_container() {
1040 let mocked_env = MockEnv::new(&[
1041 (FUNCTIONS_WORKER_RUNTIME, "node"),
1042 (COMPUTERNAME, "10-20-30-40"),
1043 (WEBSITE_POD_NAME, "pod-1"),
1044 (CONTAINER_NAME, "container-1"),
1045 ]);
1046 let metadata = AzureMetadata::new_function(mocked_env).unwrap();
1047 assert_eq!(metadata.get_instance_name(), "10-20-30-40");
1048 }
1049
1050 #[test]
1051 fn test_instance_name_pod_name_preferred_over_container_name() {
1052 let mocked_env = MockEnv::new(&[
1053 (FUNCTIONS_WORKER_RUNTIME, "node"),
1054 (WEBSITE_POD_NAME, "0--abc-test"),
1055 (CONTAINER_NAME, "container-1"),
1056 ]);
1057 let metadata = AzureMetadata::new_function(mocked_env).unwrap();
1058 assert_eq!(metadata.get_instance_name(), "0--abc-test");
1059 }
1060
1061 #[test]
1062 fn test_instance_name_falls_back_to_container_name() {
1063 let mocked_env = MockEnv::new(&[
1064 (FUNCTIONS_WORKER_RUNTIME, "node"),
1065 (CONTAINER_NAME, "0--abc-test"),
1066 ]);
1067 let metadata = AzureMetadata::new_function(mocked_env).unwrap();
1068 assert_eq!(metadata.get_instance_name(), "0--abc-test");
1069 }
1070
1071 #[test]
1072 fn test_instance_name_empty_string_treated_as_missing() {
1073 let mocked_env = MockEnv::new(&[
1074 (FUNCTIONS_WORKER_RUNTIME, "node"),
1075 (CONTAINER_NAME, ""),
1076 (WEBSITE_POD_NAME, ""),
1077 (COMPUTERNAME, "worker-1"),
1078 ]);
1079 let metadata = AzureMetadata::new_function(mocked_env).unwrap();
1080 assert_eq!(metadata.get_instance_name(), "worker-1");
1081 }
1082
1083 #[test]
1084 fn test_instance_name_whitespace_only_treated_as_missing() {
1085 let mocked_env = MockEnv::new(&[
1086 (FUNCTIONS_WORKER_RUNTIME, "node"),
1087 (CONTAINER_NAME, " "),
1088 (WEBSITE_POD_NAME, " "),
1089 (COMPUTERNAME, "worker-2"),
1090 ]);
1091 let metadata = AzureMetadata::new_function(mocked_env).unwrap();
1092 assert_eq!(metadata.get_instance_name(), "worker-2");
1093 }
1094
1095 #[test]
1096 fn test_instance_name_no_instance_vars_is_unknown() {
1097 let mocked_env = MockEnv::new(&[(FUNCTIONS_WORKER_RUNTIME, "node")]);
1098 let metadata = AzureMetadata::new_function(mocked_env).unwrap();
1099 assert_eq!(metadata.get_instance_name(), UNKNOWN_VALUE);
1100 }
1101}