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