1use crate::utils::{get_json, url};
2use crate::{Result, TaskFilter};
3use reqwest;
4use serde_derive::Deserialize;
5use std::collections::BTreeMap;
6
7pub(crate) trait TaskclusterCI {
8 fn default_artifact_name(&self) -> &'static str;
9 fn default_task_filter(&self) -> Vec<TaskFilter>;
10 fn get_taskgroups(
11 &self,
12 client: &reqwest::blocking::Client,
13 commit: &str,
14 ) -> Result<Vec<String>>;
15 fn taskcluster(&self) -> &Taskcluster;
16}
17
18#[derive(Debug, PartialEq, Deserialize)]
19#[serde(rename_all = "lowercase")]
20pub enum TaskState {
21 Unscheduled,
22 Pending,
23 Running,
24 Completed,
25 Failed,
26 Exception,
27}
28
29impl TaskState {
30 pub fn is_complete(&self) -> bool {
31 match self {
32 TaskState::Unscheduled | TaskState::Pending | TaskState::Running => false,
33 TaskState::Completed | TaskState::Failed | TaskState::Exception => true,
34 }
35 }
36}
37
38#[derive(Debug, Deserialize)]
39#[allow(non_snake_case)]
40pub struct IndexResponse {
41 pub namespace: String,
42 pub taskId: String,
43 pub rank: u64,
44 pub expires: String,
45}
46
47#[derive(Debug, Deserialize)]
48#[allow(non_snake_case)]
49pub struct ArtifactsResponse {
50 pub artifacts: Vec<Artifact>,
51}
52
53#[derive(Debug, Deserialize)]
54#[allow(non_snake_case)]
55pub struct Artifact {
56 pub storageType: String,
57 pub name: String,
58 pub expires: String,
59 pub contentType: String,
60}
61
62#[derive(Debug, Deserialize)]
63#[allow(non_snake_case)]
64pub struct TaskGroupResponse {
65 pub taskGroupId: String,
66 pub tasks: Vec<TaskGroupTask>,
67 pub continuationToken: Option<String>,
68}
69
70#[derive(Debug, Deserialize)]
71#[allow(non_snake_case)]
72pub struct TaskGroupTask {
73 pub status: TaskGroupTaskStatus,
74 pub task: Task,
75}
76
77#[derive(Debug, Deserialize)]
78#[allow(non_snake_case)]
79pub struct TaskGroupTaskStatus {
80 pub taskId: String,
81 pub provisionerId: String,
82 pub workerType: String,
83 pub schedulerId: String,
84 pub taskGroupId: String,
85 pub deadline: String, pub expires: String, pub retriesLeft: u64,
88 pub state: TaskState,
89 pub runs: Vec<TaskRun>,
90}
91
92#[derive(Debug, Deserialize)]
93#[allow(non_snake_case)]
94pub struct TaskRun {
95 pub runId: u64,
96 pub state: TaskState,
97 pub reasonCreated: String, pub reasonResolved: Option<String>, pub workerGroup: Option<String>,
100 pub workerId: Option<String>,
101 pub takenUntil: Option<String>, pub scheduled: Option<String>, pub started: Option<String>, pub resolved: Option<String>, }
106
107#[derive(Debug, Deserialize)]
108#[allow(non_snake_case)]
109pub struct Task {
110 pub provisionerId: String,
111 pub workerType: String,
112 pub schedulerId: String,
113 pub taskGroupId: String,
114 pub metadata: TaskMetadata,
115 #[serde(default)]
116 pub extra: BTreeMap<String, serde_json::Value>,
117}
118
119#[derive(Debug, Deserialize)]
120#[allow(non_snake_case)]
121pub struct TaskMetadata {
122 pub owner: String,
123 pub source: String,
124 pub description: String,
125 pub name: String,
126}
127
128pub struct Taskcluster {
129 pub index_base: String,
130 pub queue_base: String,
131}
132
133impl Taskcluster {
134 pub fn new(taskcluster_base: &str) -> Taskcluster {
135 if taskcluster_base == "https://taskcluster.net" {
136 Taskcluster {
137 index_base: "https://index.taskcluster.net/v1/".into(),
138 queue_base: "https://queue.taskcluster.net/v1/".into(),
139 }
140 } else {
141 Taskcluster {
142 index_base: format!("{}/api/index/v1/", taskcluster_base),
143 queue_base: format!("{}/api/queue/v1/", taskcluster_base),
144 }
145 }
146 }
147
148 pub fn get_taskgroup_tasks(
149 &self,
150 client: &reqwest::blocking::Client,
151 taskgroup_id: &str,
152 ) -> Result<Vec<TaskGroupTask>> {
153 let url_suffix = format!("task-group/{}/list", taskgroup_id);
154 let mut tasks = Vec::new();
155 let mut continuation_token: Option<String> = None;
156 loop {
157 let query = continuation_token.map(|token| vec![("continuationToken".into(), token)]);
158 let data: TaskGroupResponse =
159 get_json(client, &url(&self.queue_base, &url_suffix), query, None)?;
160 tasks.extend(data.tasks);
161 if data.continuationToken.is_none() {
162 break;
163 }
164 continuation_token = data.continuationToken;
165 }
166 Ok(tasks)
167 }
168
169 pub fn get_artifacts(
170 &self,
171 client: &reqwest::blocking::Client,
172 task_id: &str,
173 ) -> Result<Vec<Artifact>> {
174 let url_suffix = format!("task/{}/artifacts", task_id);
175 let artifacts: ArtifactsResponse =
176 get_json(client, &url(&self.queue_base, &url_suffix), None, None)?;
177 Ok(artifacts.artifacts)
178 }
179
180 pub fn get_log_url(&self, task_id: &str, artifact: &Artifact) -> String {
181 let task_url = format!("task/{}/artifacts", task_id);
182 url(
183 &self.queue_base,
184 &format!("{}/{}", &task_url, artifact.name),
185 )
186 }
187}
188
189pub fn tasks_complete<'a, I>(mut tasks: I) -> bool
190where
191 I: Iterator<Item = &'a TaskGroupTask>,
192{
193 tasks.all(|task| task.status.state.is_complete())
194}