libdd_common/
azure_app_services.rs

1// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use regex::Regex;
5use std::env;
6use std::sync::LazyLock;
7
8const WEBSITE_OWNER_NAME: &str = "WEBSITE_OWNER_NAME";
9const WEBSITE_SITE_NAME: &str = "WEBSITE_SITE_NAME";
10const WEBSITE_RESOURCE_GROUP: &str = "WEBSITE_RESOURCE_GROUP";
11const SITE_EXTENSION_VERSION: &str = "DD_AAS_DOTNET_EXTENSION_VERSION";
12const WEBSITE_OS: &str = "WEBSITE_OS";
13const INSTANCE_NAME: &str = "COMPUTERNAME";
14const INSTANCE_ID: &str = "WEBSITE_INSTANCE_ID";
15const SERVICE_CONTEXT: &str = "DD_AZURE_APP_SERVICES";
16const FUNCTIONS_WORKER_RUNTIME: &str = "FUNCTIONS_WORKER_RUNTIME";
17const FUNCTIONS_WORKER_RUNTIME_VERSION: &str = "FUNCTIONS_WORKER_RUNTIME_VERSION";
18const FUNCTIONS_EXTENSION_VERSION: &str = "FUNCTIONS_EXTENSION_VERSION";
19const DD_AZURE_RESOURCE_GROUP: &str = "DD_AZURE_RESOURCE_GROUP";
20const WEBSITE_SKU: &str = "WEBSITE_SKU";
21
22const UNKNOWN_VALUE: &str = "unknown";
23
24enum AzureContext {
25    AzureFunctions,
26    AzureAppService,
27}
28
29macro_rules! get_trimmed_env_var {
30    ($name:expr) => {
31        env::var($name)
32            .ok()
33            .map(|v| v.trim().to_string())
34            .filter(|s| !s.is_empty())
35    };
36}
37
38macro_rules! get_value_or_unknown {
39    ($name:expr) => {
40        $name.as_ref().map(|s| s.as_str()).unwrap_or(UNKNOWN_VALUE)
41    };
42}
43
44trait ToBoolean {
45    fn to_bool(&self) -> bool;
46}
47
48impl ToBoolean for String {
49    fn to_bool(&self) -> bool {
50        matches!(
51            self.to_lowercase().as_str(),
52            "true" | "t" | "y" | "1" | "yes"
53        )
54    }
55}
56
57pub trait QueryEnv {
58    fn get_var(&self, var: &str) -> Option<String>;
59}
60
61struct RealEnv;
62
63impl QueryEnv for RealEnv {
64    fn get_var(&self, var: &str) -> Option<String> {
65        get_trimmed_env_var!(var)
66    }
67}
68
69#[derive(Default)]
70pub struct AzureMetadata {
71    resource_id: Option<String>,
72    subscription_id: Option<String>,
73    site_name: Option<String>,
74    resource_group: Option<String>,
75    extension_version: Option<String>,
76    operating_system: String,
77    instance_name: Option<String>,
78    instance_id: Option<String>,
79    site_kind: String,
80    site_type: String,
81    runtime: Option<String>,
82    runtime_version: Option<String>,
83    function_runtime_version: Option<String>,
84}
85
86impl AzureMetadata {
87    fn get_azure_context<T: QueryEnv>(query: &T) -> AzureContext {
88        match (
89            query.get_var(FUNCTIONS_WORKER_RUNTIME),
90            query.get_var(FUNCTIONS_EXTENSION_VERSION),
91        ) {
92            (Some(_), Some(_)) => AzureContext::AzureFunctions,
93            (Some(_), None) => AzureContext::AzureFunctions,
94            (None, Some(_)) => AzureContext::AzureFunctions,
95            (None, None) => AzureContext::AzureAppService,
96        }
97    }
98
99    fn extract_subscription_id(s: Option<String>) -> Option<String> {
100        s?.split('+')
101            .next()
102            .filter(|s| !s.trim().is_empty())
103            .map(|v| v.to_string())
104    }
105
106    fn extract_resource_group(s: Option<String>) -> Option<String> {
107        #[allow(clippy::unwrap_used)]
108        let re: Regex = Regex::new(r".+\+(.+)-.+webspace(-Linux)?").unwrap();
109
110        s.as_ref().and_then(|text| {
111            re.captures(text)
112                .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string()))
113        })
114    }
115
116    /*
117     * Computation of the resource id follow the same way the .NET tracer is doing:
118     * https://github.com/DataDog/dd-trace-dotnet/blob/834a4b05b4ed91a819eb78761bf1ddb805969f65/tracer/src/Datadog.Trace/PlatformHelpers/AzureAppServices.cs#L215
119     */
120    fn build_resource_id(
121        subscription_id: Option<&String>,
122        site_name: Option<&String>,
123        resource_group: Option<&String>,
124    ) -> Option<String> {
125        match (subscription_id, site_name, resource_group) {
126            (Some(id_sub), Some(sitename), Some(res_grp)) => Some(
127                format!("/subscriptions/{id_sub}/resourcegroups/{res_grp}/providers/microsoft.web/sites/{sitename}")
128                .to_lowercase(),
129            ),
130            _ => None,
131        }
132    }
133
134    fn build_metadata<T: QueryEnv>(query: T) -> Option<Self> {
135        let subscription_id =
136            AzureMetadata::extract_subscription_id(query.get_var(WEBSITE_OWNER_NAME));
137        let site_name = query.get_var(WEBSITE_SITE_NAME);
138
139        let (site_kind, site_type) = match AzureMetadata::get_azure_context(&query) {
140            AzureContext::AzureFunctions => ("functionapp".to_owned(), "function".to_owned()),
141            _ => ("app".to_owned(), "app".to_owned()),
142        };
143
144        let resource_group = query
145            .get_var(DD_AZURE_RESOURCE_GROUP)
146            .or_else(|| query.get_var(WEBSITE_RESOURCE_GROUP))
147            .or_else(|| {
148                // Check if we're in flex consumption plan first
149                match query.get_var(WEBSITE_SKU).as_deref() {
150                    Some("FlexConsumption") => None,
151                    /* Flex Consumption plans need the `DD_AZURE_RESOURCE_GROUP` env var. If this
152                     * logic ever changes, update the logic in
153                     * `serverless-components/src/datadog-trace-agent` and the serverless compat
154                     * layers accordingly. */
155                    _ => AzureMetadata::extract_resource_group(query.get_var(WEBSITE_OWNER_NAME)),
156                }
157            });
158
159        let resource_id = AzureMetadata::build_resource_id(
160            subscription_id.as_ref(),
161            site_name.as_ref(),
162            resource_group.as_ref(),
163        );
164        let extension_version = query.get_var(SITE_EXTENSION_VERSION);
165        let operating_system = query
166            .get_var(WEBSITE_OS)
167            .unwrap_or(std::env::consts::OS.to_string());
168        let instance_name = query.get_var(INSTANCE_NAME);
169        let instance_id = query.get_var(INSTANCE_ID);
170
171        let runtime = query.get_var(FUNCTIONS_WORKER_RUNTIME);
172        let runtime_version = query.get_var(FUNCTIONS_WORKER_RUNTIME_VERSION);
173        let function_runtime_version = query.get_var(FUNCTIONS_EXTENSION_VERSION);
174
175        Some(AzureMetadata {
176            resource_id,
177            subscription_id,
178            site_name,
179            resource_group,
180            extension_version,
181            operating_system,
182            instance_name,
183            instance_id,
184            site_kind,
185            site_type,
186            runtime,
187            runtime_version,
188            function_runtime_version,
189        })
190    }
191
192    pub fn new<T: QueryEnv>(query: T) -> Option<Self> {
193        let is_relevant = query
194            .get_var(SERVICE_CONTEXT)
195            .map(|s| s.to_bool())
196            .unwrap_or(false);
197
198        if !is_relevant {
199            return None;
200        }
201
202        AzureMetadata::build_metadata(query)
203    }
204
205    pub fn new_function<T: QueryEnv>(query: T) -> Option<Self> {
206        match matches!(
207            AzureMetadata::get_azure_context(&query),
208            AzureContext::AzureFunctions
209        ) {
210            true => AzureMetadata::build_metadata(query),
211            false => None,
212        }
213    }
214
215    pub fn get_resource_id(&self) -> &str {
216        get_value_or_unknown!(self.resource_id)
217    }
218
219    pub fn get_subscription_id(&self) -> &str {
220        get_value_or_unknown!(self.subscription_id)
221    }
222
223    pub fn get_site_name(&self) -> &str {
224        get_value_or_unknown!(self.site_name)
225    }
226
227    pub fn get_resource_group(&self) -> &str {
228        get_value_or_unknown!(self.resource_group)
229    }
230
231    pub fn get_extension_version(&self) -> &str {
232        get_value_or_unknown!(self.extension_version)
233    }
234
235    pub fn get_operating_system(&self) -> &str {
236        self.operating_system.as_str()
237    }
238
239    pub fn get_instance_name(&self) -> &str {
240        get_value_or_unknown!(self.instance_name)
241    }
242
243    pub fn get_instance_id(&self) -> &str {
244        get_value_or_unknown!(self.instance_id)
245    }
246
247    pub fn get_site_type(&self) -> &str {
248        self.site_type.as_str()
249    }
250
251    pub fn get_site_kind(&self) -> &str {
252        self.site_kind.as_str()
253    }
254
255    pub fn get_runtime(&self) -> &str {
256        get_value_or_unknown!(self.runtime)
257    }
258
259    pub fn get_runtime_version(&self) -> &str {
260        get_value_or_unknown!(self.runtime_version)
261    }
262
263    pub fn get_function_runtime_version(&self) -> &str {
264        get_value_or_unknown!(self.function_runtime_version)
265    }
266}
267
268pub static AAS_METADATA: LazyLock<Option<AzureMetadata>> =
269    LazyLock::new(|| AzureMetadata::new(RealEnv {}));
270
271pub static AAS_METADATA_FUNCTION: LazyLock<Option<AzureMetadata>> =
272    LazyLock::new(|| AzureMetadata::new_function(RealEnv {}));
273
274#[cfg(test)]
275mod tests {
276
277    use indexmap::IndexMap;
278
279    use crate::azure_app_services::{QueryEnv, WEBSITE_OWNER_NAME};
280
281    use super::*;
282
283    struct MockEnv {
284        pub env_vars: IndexMap<String, String>,
285    }
286
287    impl MockEnv {
288        pub fn new(vars: &[(&str, &str)]) -> Self {
289            let mut env_vars: IndexMap<String, String> = IndexMap::new();
290            vars.iter().for_each(|(name, value)| {
291                env_vars.insert(name.to_string(), value.to_string());
292            });
293
294            MockEnv { env_vars }
295        }
296    }
297
298    impl QueryEnv for MockEnv {
299        fn get_var(&self, var: &str) -> Option<String> {
300            self.env_vars.get(var).cloned()
301        }
302    }
303
304    #[test]
305    fn test_metadata_is_not_relevant_by_default() {
306        let mocked_env = MockEnv::new(&[]);
307
308        let metadata = AzureMetadata::new(mocked_env);
309        assert!(metadata.is_none());
310    }
311
312    #[test]
313    fn test_metadata_is_relevant_first() {
314        let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "true")]);
315
316        let metadata = AzureMetadata::new(mocked_env);
317        assert!(metadata.is_some());
318    }
319
320    #[test]
321    fn test_metadata_is_relevant_second() {
322        let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "t")]);
323
324        let metadata = AzureMetadata::new(mocked_env);
325        assert!(metadata.is_some());
326    }
327
328    #[test]
329    fn test_metadata_is_relevant_third() {
330        let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "TrUe")]);
331
332        let metadata = AzureMetadata::new(mocked_env);
333        assert!(metadata.is_some());
334    }
335
336    #[test]
337    fn test_metadata_is_relevant_fourth() {
338        let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "1")]);
339
340        let metadata = AzureMetadata::new(mocked_env);
341        assert!(metadata.is_some());
342    }
343
344    #[test]
345    fn test_metadata_is_relevant_fifth() {
346        let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "yEs")]);
347
348        let metadata = AzureMetadata::new(mocked_env);
349        assert!(metadata.is_some());
350    }
351
352    #[test]
353    fn test_metadata_is_relevant_sixth() {
354        let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "Y")]);
355
356        let metadata = AzureMetadata::new(mocked_env);
357        assert!(metadata.is_some());
358    }
359
360    #[test]
361    fn test_metadata_is_not_relevant_if_explicit() {
362        let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "0")]);
363
364        let metadata = AzureMetadata::new(mocked_env);
365        assert!(metadata.is_none());
366    }
367
368    #[test]
369    fn test_extract_subscription_without_plus_sign() {
370        let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, "foo"), (SERVICE_CONTEXT, "1")]);
371
372        let metadata = AzureMetadata::new(mocked_env).unwrap();
373
374        let expected_id = "foo";
375
376        assert_eq!(metadata.get_subscription_id(), expected_id);
377    }
378
379    #[test]
380    fn test_extract_subscription_with_plus_sign() {
381        let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, "foo+bar"), (SERVICE_CONTEXT, "1")]);
382
383        let metadata = AzureMetadata::new(mocked_env).unwrap();
384
385        let expected_id = "foo";
386        assert_eq!(metadata.get_subscription_id(), expected_id);
387    }
388
389    #[test]
390    fn test_extract_subscription_with_empty_string() {
391        let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, ""), (SERVICE_CONTEXT, "1")]);
392
393        let metadata = AzureMetadata::new(mocked_env).unwrap();
394
395        assert_eq!(metadata.get_subscription_id(), UNKNOWN_VALUE);
396    }
397
398    #[test]
399    fn test_extract_subscription_with_only_whitespaces() {
400        let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, "    "), (SERVICE_CONTEXT, "1")]);
401
402        let metadata = AzureMetadata::new(mocked_env).unwrap();
403
404        assert_eq!(metadata.get_subscription_id(), UNKNOWN_VALUE);
405    }
406
407    #[test]
408    fn test_extract_subscription_with_only_plus_sign() {
409        let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, "+"), (SERVICE_CONTEXT, "1")]);
410
411        let metadata = AzureMetadata::new(mocked_env).unwrap();
412
413        assert_eq!(metadata.get_subscription_id(), UNKNOWN_VALUE);
414    }
415
416    #[test]
417    fn test_extract_subscription_with_whitespaces_separated_by_plus() {
418        let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, "   + "), (SERVICE_CONTEXT, "1")]);
419
420        let metadata = AzureMetadata::new(mocked_env).unwrap();
421
422        assert_eq!(metadata.get_subscription_id(), UNKNOWN_VALUE);
423    }
424
425    #[test]
426    fn test_extract_subscription_plus_sign_and_other_string() {
427        let mocked_env = MockEnv::new(&[(WEBSITE_OWNER_NAME, "+other"), (SERVICE_CONTEXT, "1")]);
428
429        let metadata = AzureMetadata::new(mocked_env).unwrap();
430
431        assert_eq!(metadata.get_subscription_id(), UNKNOWN_VALUE);
432    }
433
434    #[test]
435    fn test_extract_resource_group_pattern_match_linux() {
436        let mocked_env = MockEnv::new(&[
437            (
438                WEBSITE_OWNER_NAME,
439                "00000000-0000-0000-0000-000000000000+test-rg-EastUSwebspace-Linux",
440            ),
441            ("FUNCTIONS_WORKER_RUNTIME", "node"),
442            ("FUNCTIONS_EXTENSION_VERSION", "~4"),
443        ]);
444
445        let metadata = AzureMetadata::new_function(mocked_env).unwrap();
446
447        let expected_resource_group = "test-rg";
448
449        assert_eq!(metadata.get_resource_group(), expected_resource_group);
450    }
451
452    #[test]
453    fn test_extract_resource_group_pattern_match_windows() {
454        let mocked_env = MockEnv::new(&[
455            (
456                WEBSITE_OWNER_NAME,
457                "00000000-0000-0000-0000-000000000000+test-rg-EastUSwebspace",
458            ),
459            ("FUNCTIONS_WORKER_RUNTIME", "node"),
460            ("FUNCTIONS_EXTENSION_VERSION", "~4"),
461        ]);
462
463        let metadata = AzureMetadata::new_function(mocked_env).unwrap();
464
465        let expected_resource_group = "test-rg";
466
467        assert_eq!(metadata.get_resource_group(), expected_resource_group);
468    }
469
470    #[test]
471    fn test_extract_resource_group_no_pattern_match() {
472        let mocked_env = MockEnv::new(&[
473            (WEBSITE_OWNER_NAME, "foo"),
474            (FUNCTIONS_WORKER_RUNTIME, "node"),
475            (FUNCTIONS_EXTENSION_VERSION, "~4"),
476        ]);
477
478        let metadata = AzureMetadata::new_function(mocked_env).unwrap();
479
480        assert_eq!(metadata.get_resource_group(), UNKNOWN_VALUE);
481    }
482
483    #[test]
484    fn test_use_resource_group_from_env_var_if_available() {
485        let mocked_env = MockEnv::new(&[
486            (WEBSITE_RESOURCE_GROUP, "test-rg-env-var"),
487            (
488                WEBSITE_OWNER_NAME,
489                "00000000-0000-0000-0000-000000000000+test-rg-EastUSwebspace-Linux",
490            ),
491            (SERVICE_CONTEXT, "1"),
492            (WEBSITE_SKU, "ElasticPremium"),
493        ]);
494
495        let metadata = AzureMetadata::new(mocked_env).unwrap();
496
497        let expected_resource_group = "test-rg-env-var";
498
499        assert_eq!(metadata.get_resource_group(), expected_resource_group);
500    }
501
502    #[test]
503    fn test_flex_consumption_resource_group_is_none_without_dd_azure_resource_group() {
504        let mocked_env = MockEnv::new(&[
505            (
506                WEBSITE_OWNER_NAME,
507                "00000000-0000-0000-0000-000000000000+flex-EastUSwebspace-Linux",
508            ),
509            (WEBSITE_SKU, "FlexConsumption"),
510            (SERVICE_CONTEXT, "1"),
511        ]);
512
513        let metadata = AzureMetadata::new(mocked_env).unwrap();
514
515        assert_eq!(metadata.get_resource_group(), UNKNOWN_VALUE);
516    }
517
518    #[test]
519    fn test_flex_consumption_uses_dd_azure_resource_group() {
520        let mocked_env = MockEnv::new(&[
521            (
522                WEBSITE_OWNER_NAME,
523                "00000000-0000-0000-0000-000000000000+flex-EastUSwebspace-Linux",
524            ),
525            (DD_AZURE_RESOURCE_GROUP, "test-flex-rg"),
526            (WEBSITE_SKU, "FlexConsumption"),
527            (SERVICE_CONTEXT, "1"),
528        ]);
529
530        let metadata = AzureMetadata::new(mocked_env).unwrap();
531
532        // Should use the DD_AZURE_RESOURCE_GROUP value instead of extracting from
533        // WEBSITE_OWNER_NAME
534        assert_eq!(metadata.get_resource_group(), "test-flex-rg");
535    }
536
537    #[test]
538    fn test_dd_azure_resource_group_has_highest_priority() {
539        let mocked_env = MockEnv::new(&[
540            (WEBSITE_RESOURCE_GROUP, "test-rg-env-var"),
541            (
542                WEBSITE_OWNER_NAME,
543                "00000000-0000-0000-0000-000000000000+test-rg-EastUSwebspace-Linux",
544            ),
545            (DD_AZURE_RESOURCE_GROUP, "dd-azure-rg-override"),
546            (SERVICE_CONTEXT, "1"),
547        ]);
548
549        let metadata = AzureMetadata::new(mocked_env).unwrap();
550
551        // DD_AZURE_RESOURCE_GROUP should have highest priority over WEBSITE_RESOURCE_GROUP and
552        // WEBSITE_OWNER_NAME
553        let expected_resource_group = "dd-azure-rg-override";
554
555        assert_eq!(metadata.get_resource_group(), expected_resource_group);
556    }
557
558    #[test]
559    fn test_build_resource_id() {
560        let mocked_env = MockEnv::new(&[
561            (WEBSITE_OWNER_NAME, "foo"),
562            (WEBSITE_SITE_NAME, "my_website"),
563            (WEBSITE_RESOURCE_GROUP, "resource_group"),
564            (SERVICE_CONTEXT, "1"),
565        ]);
566
567        let metadata = AzureMetadata::new(mocked_env).unwrap();
568
569        assert_eq!(
570            metadata.get_resource_id(),
571            "/subscriptions/foo/resourcegroups/resource_group/providers/microsoft.web/sites/my_website"
572        )
573    }
574
575    #[test]
576    fn test_build_resource_id_with_missing_subscription_id() {
577        let mocked_env = MockEnv::new(&[
578            (WEBSITE_SITE_NAME, "my_website"),
579            (WEBSITE_RESOURCE_GROUP, "resource_group"),
580            (SERVICE_CONTEXT, "1"),
581        ]);
582
583        let metadata = AzureMetadata::new(mocked_env).unwrap();
584
585        assert_eq!(metadata.get_resource_id(), UNKNOWN_VALUE)
586    }
587
588    #[test]
589    fn test_build_resource_id_with_missing_site_name() {
590        let mocked_env = MockEnv::new(&[
591            (WEBSITE_OWNER_NAME, "foo"),
592            (WEBSITE_RESOURCE_GROUP, "resource_group"),
593            (SERVICE_CONTEXT, "1"),
594        ]);
595
596        let metadata = AzureMetadata::new(mocked_env).unwrap();
597
598        assert_eq!(metadata.get_resource_id(), UNKNOWN_VALUE)
599    }
600
601    #[test]
602    fn test_build_resource_id_with_missing_resource_group() {
603        let mocked_env = MockEnv::new(&[
604            (WEBSITE_OWNER_NAME, "foo"),
605            (WEBSITE_SITE_NAME, "my_website"),
606            (SERVICE_CONTEXT, "1"),
607        ]);
608
609        let metadata = AzureMetadata::new(mocked_env).unwrap();
610
611        assert_eq!(metadata.get_resource_id(), UNKNOWN_VALUE)
612    }
613
614    #[test]
615    fn test_build_resource_id_with_missing_info() {
616        let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "1")]);
617        let metadata = AzureMetadata::new(mocked_env).unwrap();
618
619        assert_eq!(metadata.get_resource_id(), UNKNOWN_VALUE)
620    }
621
622    #[test]
623    fn test_site_type_and_kind_default() {
624        let mocked_env = MockEnv::new(&[(SERVICE_CONTEXT, "1")]);
625        let metadata = AzureMetadata::new(mocked_env).unwrap();
626
627        assert_eq!(metadata.get_site_type(), "app");
628        assert_eq!(metadata.get_site_kind(), "app")
629    }
630
631    #[test]
632    fn test_site_type_and_kind_if_worker_runtime_not_specified() {
633        let mocked_env = MockEnv::new(&[
634            (FUNCTIONS_WORKER_RUNTIME, "my_runtime"),
635            (SERVICE_CONTEXT, "1"),
636        ]);
637        let metadata = AzureMetadata::new(mocked_env).unwrap();
638
639        assert_eq!(metadata.get_site_kind(), "functionapp");
640        assert_eq!(metadata.get_site_type(), "function")
641    }
642
643    #[test]
644    fn test_site_type_and_kind_if_extension_version_not_specified() {
645        let mocked_env = MockEnv::new(&[
646            (FUNCTIONS_EXTENSION_VERSION, "next_version"),
647            (SERVICE_CONTEXT, "1"),
648        ]);
649        let metadata = AzureMetadata::new(mocked_env).unwrap();
650
651        assert_eq!(metadata.get_site_kind(), "functionapp");
652        assert_eq!(metadata.get_site_type(), "function")
653    }
654
655    #[test]
656    fn test_site_type_and_kind_if_both_specified() {
657        let mocked_env = MockEnv::new(&[
658            (FUNCTIONS_WORKER_RUNTIME, "my_runtime"),
659            (FUNCTIONS_EXTENSION_VERSION, "next_version"),
660            (SERVICE_CONTEXT, "1"),
661        ]);
662        let metadata = AzureMetadata::new(mocked_env).unwrap();
663
664        assert_eq!(metadata.get_site_kind(), "functionapp");
665        assert_eq!(metadata.get_site_type(), "function")
666    }
667
668    #[test]
669    fn test_check_other_simple_env_retrieval() {
670        let expected_site_name = "my_site_name".to_owned();
671        let expected_resource_group = "my_resource_group".to_owned();
672        let expected_site_version = "v42".to_owned();
673        let expected_operating_system = "FreeBSD".to_owned();
674        let expected_instance_name = "my_instance_name".to_owned();
675        let expected_instance_id = "my_instance_id".to_owned();
676        let expected_function_extension_version = "~4".to_owned();
677        let expected_runtime = "node".to_owned();
678        let expected_runtime_version = "18".to_owned();
679
680        let mocked_env = MockEnv::new(&[
681            (WEBSITE_SITE_NAME, expected_site_name.as_str()),
682            (WEBSITE_RESOURCE_GROUP, expected_resource_group.as_str()),
683            (SITE_EXTENSION_VERSION, expected_site_version.as_str()),
684            (WEBSITE_OS, expected_operating_system.as_str()),
685            (INSTANCE_NAME, expected_instance_name.as_str()),
686            (INSTANCE_ID, expected_instance_id.as_str()),
687            (SERVICE_CONTEXT, "1"),
688            (
689                FUNCTIONS_EXTENSION_VERSION,
690                expected_function_extension_version.as_str(),
691            ),
692            (FUNCTIONS_WORKER_RUNTIME, expected_runtime.as_str()),
693            (
694                FUNCTIONS_WORKER_RUNTIME_VERSION,
695                expected_runtime_version.as_str(),
696            ),
697        ]);
698
699        let metadata = AzureMetadata::new(mocked_env).unwrap();
700
701        assert_eq!(expected_site_name, metadata.get_site_name());
702        assert_eq!(expected_resource_group, metadata.get_resource_group());
703        assert_eq!(expected_site_version, metadata.get_extension_version());
704        assert_eq!(expected_operating_system, metadata.get_operating_system());
705        assert_eq!(expected_instance_name, metadata.get_instance_name());
706        assert_eq!(expected_instance_id, metadata.get_instance_id());
707        assert_eq!(
708            expected_function_extension_version,
709            metadata.get_function_runtime_version()
710        );
711        assert_eq!(expected_runtime, metadata.get_runtime());
712        assert_eq!(expected_runtime_version, metadata.get_runtime_version());
713    }
714
715    #[test]
716    fn test_get_trimmed_env_var_empty_string() {
717        env::remove_var("TEST_VAR_NONE");
718        assert_eq!(get_trimmed_env_var!("TEST_VAR_NONE"), None);
719
720        env::set_var("TEST_VAR_EMPTY_STRING", "");
721        assert_eq!(get_trimmed_env_var!("TEST_VAR_EMPTY_STRING"), None);
722    }
723}