1use crate::DetectionStrategy;
4
5#[derive(Debug, thiserror::Error)]
7pub enum Error {
8 #[error("failed to obtain OIDC token from `buildkite-agent` CLI")]
10 Execution(#[from] std::io::Error),
11}
12
13pub(crate) struct Buildkite;
14
15impl DetectionStrategy for Buildkite {
16 type Error = Error;
17
18 fn new(_state: &crate::DetectionState) -> Option<Self>
19 where
20 Self: Sized,
21 {
22 std::env::var("BUILDKITE")
24 .ok()
25 .filter(|v| v == "true")
26 .map(|_| Buildkite)
27 }
28
29 async fn detect(&self, audience: &str) -> Result<crate::IdToken, Self::Error> {
38 let output = std::process::Command::new("buildkite-agent")
39 .args(&["oidc", "request-token", "--audience", audience])
40 .output()?;
41
42 if !output.status.success() {
43 return Err(Error::Execution(std::io::Error::new(
44 std::io::ErrorKind::Other,
45 format!(
46 "`buildkite-agent` exited with code {status}: '{stderr}'",
47 status = output.status,
48 stderr = String::from_utf8_lossy(&output.stderr),
49 ),
50 )));
51 }
52
53 let token = String::from_utf8_lossy(&output.stdout).trim().to_string();
54 Ok(crate::IdToken(token.into()))
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use crate::{DetectionStrategy as _, buildkite::Buildkite, tests::EnvScope};
61
62 #[tokio::test]
63 async fn test_not_detected() {
64 let mut scope = EnvScope::new();
65 scope.unsetenv("BUILDKITE");
66
67 let state = Default::default();
68 assert!(Buildkite::new(&state).is_none());
69 }
70
71 #[tokio::test]
72 async fn test_detected() {
73 let mut scope = EnvScope::new();
74 scope.setenv("BUILDKITE", "true");
75
76 let state = Default::default();
77 assert!(Buildkite::new(&state).is_some());
78 }
79
80 #[tokio::test]
82 #[cfg_attr(not(feature = "test-buildkite-1p"), ignore)]
83 async fn test_1p_detection_ok() {
84 let _ = EnvScope::new();
85 let state = Default::default();
86 let detector = Buildkite::new(&state).expect("should detect Buildkite");
87 let token = detector
88 .detect("test_1p_detection_ok")
89 .await
90 .expect("should fetch token");
91
92 assert!(token.reveal().starts_with("eyJ"));
93 }
94}