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                            let sh = xshell::Shell::new()?;
90
91                            if matches!(nuget_config_platform, NugetInstallPlatform::Windows) {
92                                let install_aacp_cmd = r#""& { $(irm https://aka.ms/install-artifacts-credprovider.ps1) } -AddNetfx""#;
93                                xshell::cmd!(sh, "powershell.exe iex {install_aacp_cmd}").run()?;
94                            } else {
95                                log::warn!("automatic Azure Artifacts Credential Provider installation is not supported yet for this platform!");
96                                log::warn!("follow the guide, and press <enter> to continue");
97                                let _ = std::io::stdin().read_line(&mut String::new());
98                            }
99
100                            check_if_aacp_installed(rt, &nuget_config_platform)?;
101
102                            Ok(())
103                        }
104                    }
105                );
106            } else {
107                ctx.emit_rust_step(
108                    "Check if Azure Artifacts Credential Provider is installed",
109                    move |ctx| {
110                        let nuget_config_platform = nuget_config_platform.claim(ctx);
111                        ensure_auth.claim(ctx);
112
113                        move |rt| {
114                            let nuget_config_platform = rt.read(nuget_config_platform);
115                            if let Err(e) = check_if_aacp_installed(rt, &nuget_config_platform) {
116                                if skip_auth_check {
117                                    log::warn!("{}", e);
118                                    log::warn!("user passed --skip-auth-check, so assuming they know what they're doing...");
119                                } else {
120                                    return Err(e)
121                                }
122                            }
123
124                            Ok(())
125                        }
126                    },
127                );
128            }
129        } else {
130            anyhow::bail!("unsupported backend")
131        }
132
133        Ok(())
134    }
135}
136
137fn check_if_aacp_installed(
138    rt: &mut RustRuntimeServices<'_>,
139    nuget_config_platform: &NugetInstallPlatform,
140) -> anyhow::Result<()> {
141    let sh = xshell::Shell::new()?;
142
143    let profile: PathBuf = if matches!(nuget_config_platform, NugetInstallPlatform::Windows) {
144        let path = xshell::cmd!(sh, "cmd.exe /c echo %UserProfile%")
145            .ignore_status()
146            .read()?;
147
148        if crate::_util::running_in_wsl(rt) {
149            crate::_util::wslpath::win_to_linux(path)
150        } else {
151            path.into()
152        }
153    } else {
154        dirs::home_dir().unwrap_or_default()
155    };
156
157    for kind in ["netfx", "netcore"] {
158        let path = profile
159            .join(".nuget")
160            .join("plugins")
161            .join(kind)
162            .join("CredentialProvider.Microsoft");
163        if path.exists() {
164            log::info!("found it!");
165            return Ok(());
166        }
167    }
168
169    anyhow::bail!("Azure Artifacts Credential Provider was not detected!")
170}