skootrs_lib/service/
output.rs

1//
2// Copyright 2024 The Skootrs Authors.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![allow(clippy::module_name_repetitions)]
17
18use octocrab::models::repos::{Asset, Release};
19use skootrs_model::skootrs::{
20    label::Label, ProjectOutput, ProjectOutputGetParams, ProjectOutputReference, ProjectOutputType,
21    ProjectOutputsListParams, SkootError,
22};
23pub trait OutputService {
24    fn list(
25        &self,
26        params: ProjectOutputsListParams,
27    ) -> impl std::future::Future<Output = Result<Vec<ProjectOutputReference>, SkootError>> + Send;
28
29    fn get(
30        &self,
31        _params: ProjectOutputGetParams,
32    ) -> impl std::future::Future<Output = Result<ProjectOutput, SkootError>> + Send;
33}
34
35pub struct LocalOutputService;
36
37impl OutputService for LocalOutputService {
38    fn list(
39        &self,
40        params: ProjectOutputsListParams,
41    ) -> impl std::future::Future<Output = Result<Vec<ProjectOutputReference>, SkootError>> + Send
42    {
43        match params.initialized_project.repo {
44            skootrs_model::skootrs::InitializedRepo::Github(g) => {
45                let github_params = GithubReleaseParams {
46                    owner: g.organization.get_name(),
47                    repo: g.name,
48                    tag: params.release.tag(),
49                };
50                GithubReleaseHandler::outputs_list(github_params)
51            }
52        }
53    }
54
55    async fn get(&self, params: ProjectOutputGetParams) -> Result<ProjectOutput, SkootError> {
56        match params.initialized_project.repo {
57            skootrs_model::skootrs::InitializedRepo::Github(g) => {
58                let github_params = GithubOutputGetParams {
59                    release: GithubReleaseHandler::get_release(GithubReleaseParams {
60                        owner: g.organization.get_name(),
61                        repo: g.name.clone(),
62                        tag: params.release.tag(),
63                    })
64                    .await?,
65                    name: params.project_output,
66                };
67                GithubReleaseHandler::get_output(github_params).await
68            }
69        }
70    }
71}
72
73struct GithubReleaseHandler;
74impl GithubReleaseHandler {
75    async fn outputs_list(
76        params: GithubReleaseParams,
77    ) -> Result<Vec<ProjectOutputReference>, SkootError> {
78        let release = Self::get_release(params).await?;
79
80        let assets = release.assets;
81        let references = assets
82            .iter()
83            .map(|asset| ProjectOutputReference {
84                name: asset.name.clone(),
85                output_type: Self::get_type(asset),
86                labels: Self::get_labels(asset),
87            })
88            .collect();
89
90        Ok(references)
91    }
92
93    async fn get_release(params: GithubReleaseParams) -> Result<Release, octocrab::Error> {
94        match params.tag {
95            Some(tag) => {
96                octocrab::instance()
97                    .repos(params.owner, params.repo)
98                    .releases()
99                    .get_by_tag(tag.as_str())
100                    .await
101            }
102            None => {
103                octocrab::instance()
104                    .repos(params.owner, params.repo)
105                    .releases()
106                    .get_latest()
107                    .await
108            }
109        }
110    }
111
112    fn get_type(asset: &Asset) -> ProjectOutputType {
113        // TODO: This matching probably isn't GitHub specific and can live somewhere more generalized.
114        match asset.url {
115            // Follows: https://github.com/ossf/sbom-everywhere/blob/main/reference/sbom_naming.md
116            _ if asset.name.contains(".spdx.") => ProjectOutputType::SBOM,
117            _ if asset.name.contains(".cdx.") => ProjectOutputType::SBOM,
118            _ if asset.name.contains(".intoto.") => ProjectOutputType::InToto,
119            // TODO: Add more types
120            _ => ProjectOutputType::Unknown("Unknown".to_string()),
121        }
122    }
123
124    fn get_labels(asset: &Asset) -> Vec<Label> {
125        match asset.url {
126            _ if asset.name.contains(".spdx.") => vec![Label::S2C2FAUD4],
127            _ if asset.name.contains(".cdx.") => vec![Label::S2C2FAUD4],
128            _ if asset.name.contains(".intoto.") => vec![Label::SLSABuildLevel3],
129            _ => vec![],
130        }
131    }
132
133    async fn get_output(params: GithubOutputGetParams) -> Result<ProjectOutput, SkootError> {
134        let asset = params
135            .release
136            .assets
137            .iter()
138            .find(|a| a.name == params.name)
139            .ok_or("Asset not found".to_string())?;
140
141        // TODO: Figure out how to support assets in private repos
142        let content = reqwest::get(asset.browser_download_url.clone())
143            .await
144            .map_err(|e| e.to_string())?
145            .text()
146            .await?;
147
148        Ok(ProjectOutput {
149            reference: ProjectOutputReference {
150                name: asset.name.clone(),
151                output_type: Self::get_type(asset),
152                labels: Self::get_labels(asset),
153            },
154            output: serde_json::to_string_pretty(&content)?,
155        })
156    }
157}
158
159struct GithubReleaseParams {
160    owner: String,
161    repo: String,
162    tag: Option<String>,
163}
164
165struct GithubOutputGetParams {
166    release: Release,
167    name: String,
168}