flowey_lib_common/
install_nuget_azure_credential_provider.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Globally install the azure credential provider nuget plugin
5
6use crate::download_nuget_exe::NugetInstallPlatform;
7use flowey::node::prelude::*;
8
9flowey_request! {
10    pub enum Request {
11        EnsureAuth(WriteVar<SideEffect>),
12        LocalOnlyAutoInstall(bool),
13        LocalOnlySkipAuthCheck(bool),
14    }
15}
16
17new_flow_node!(struct Node);
18
19impl FlowNode for Node {
20    type Request = Request;
21
22    fn imports(ctx: &mut ImportCtx<'_>) {
23        ctx.import::<crate::ado_task_nuget_authenticate::Node>();
24        ctx.import::<super::download_nuget_exe::Node>();
25    }
26
27    fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
28        let mut ensure_auth = Vec::new();
29        let mut auto_install = None;
30        let mut skip_auth_check = None;
31
32        for req in requests {
33            match req {
34                Request::EnsureAuth(v) => ensure_auth.push(v),
35                Request::LocalOnlyAutoInstall(v) => {
36                    same_across_all_reqs("LocalOnlyAutoInstall", &mut auto_install, v)?;
37                }
38                Request::LocalOnlySkipAuthCheck(v) => {
39                    same_across_all_reqs("LocalOnlySkipAuthCheck", &mut skip_auth_check, v)?;
40                }
41            }
42        }
43
44        if ensure_auth.is_empty() {
45            return Ok(());
46        }
47
48        if matches!(ctx.backend(), FlowBackend::Ado) {
49            if auto_install.is_some() {
50                anyhow::bail!("can only use `LocalOnlyAutoInstall` when using the Local backend");
51            }
52
53            if skip_auth_check.is_some() {
54                anyhow::bail!("can only use `LocalOnlySkipAuthCheck` when using the Local backend");
55            }
56
57            // -- end of req processing -- //
58
59            // defer auth to the built-in task
60            for v in ensure_auth {
61                ctx.req(crate::ado_task_nuget_authenticate::Request::EnsureAuth(v));
62            }
63        } else if matches!(ctx.backend(), FlowBackend::Local) {
64            let auto_install = auto_install.ok_or(anyhow::anyhow!(
65                "Missing essential request: LocalOnlyAutoInstall",
66            ))?;
67            let skip_auth_check = skip_auth_check.ok_or(anyhow::anyhow!(
68                "Missing essential request: LocalOnlySkipAuthCheck",
69            ))?;
70
71            // -- end of req processing -- //
72
73            let nuget_config_platform =
74                ctx.reqv(super::download_nuget_exe::Request::NugetInstallPlatform);
75
76            if auto_install {
77                ctx.emit_rust_step(
78                    "Install Azure Artifacts Credential Provider",
79                    move |ctx|{
80                        let nuget_config_platform = nuget_config_platform.claim(ctx);
81                        ensure_auth.claim(ctx);
82
83                        move |rt| {
84                            let nuget_config_platform = rt.read(nuget_config_platform);
85                            if check_if_aacp_installed(rt, &nuget_config_platform).is_ok() {
86                                return Ok(())
87                            }
88
89                            if matches!(nuget_config_platform, NugetInstallPlatform::Windows) {
90                                let install_aacp_cmd = r#""& { $(irm https://aka.ms/install-artifacts-credprovider.ps1) } -AddNetfx""#;
91                                flowey::shell_cmd!(rt, "powershell.exe iex {install_aacp_cmd}").run()?;
92                            } else {
93                                log::warn!("automatic Azure Artifacts Credential Provider installation is not supported yet for this platform!");
94                                log::warn!("follow the guide, and press <enter> to continue");
95                                let _ = std::io::stdin().read_line(&mut String::new());
96                            }
97
98                            check_if_aacp_installed(rt, &nuget_config_platform)?;
99
100                            Ok(())
101                        }
102                    }
103                );
104            } else {
105                ctx.emit_rust_step(
106                    "Check if Azure Artifacts Credential Provider is installed",
107                    move |ctx| {
108                        let nuget_config_platform = nuget_config_platform.claim(ctx);
109                        ensure_auth.claim(ctx);
110
111                        move |rt| {
112                            let nuget_config_platform = rt.read(nuget_config_platform);
113                            if let Err(e) = check_if_aacp_installed(rt, &nuget_config_platform) {
114                                if skip_auth_check {
115                                    log::warn!("{}", e);
116                                    log::warn!("user passed --skip-auth-check, so assuming they know what they're doing...");
117                                } else {
118                                    return Err(e)
119                                }
120                            }
121
122                            Ok(())
123                        }
124                    },
125                );
126            }
127        } else {
128            anyhow::bail!("unsupported backend")
129        }
130
131        Ok(())
132    }
133}
134
135fn check_if_aacp_installed(
136    rt: &mut RustRuntimeServices<'_>,
137    nuget_config_platform: &NugetInstallPlatform,
138) -> anyhow::Result<()> {
139    let profile: PathBuf = if matches!(nuget_config_platform, NugetInstallPlatform::Windows) {
140        let path = flowey::shell_cmd!(rt, "cmd.exe /c echo %UserProfile%")
141            .ignore_status()
142            .read()?;
143
144        if crate::_util::running_in_wsl(rt) {
145            crate::_util::wslpath::win_to_linux(rt, path)
146        } else {
147            path.into()
148        }
149    } else {
150        dirs::home_dir().unwrap_or_default()
151    };
152
153    for kind in ["netfx", "netcore"] {
154        let path = profile
155            .join(".nuget")
156            .join("plugins")
157            .join(kind)
158            .join("CredentialProvider.Microsoft");
159        if path.exists() {
160            log::info!("found it!");
161            return Ok(());
162        }
163    }
164
165    anyhow::bail!("Azure Artifacts Credential Provider was not detected!")
166}