Loading acorn-cli/src/commands/gather/mod.rs +23 −7 Original line number Diff line number Diff line use acorn::io::api; use acorn::io::api::{self}; use acorn::param; use acorn::prelude::PathBuf; use clap_verbosity_flag::Verbosity; use color_eyre::eyre::{Report, Result}; pub async fn run(_path: &Option<PathBuf>, _ignore: &Option<String>, _offline: &bool, _verbose: &Verbosity) -> Result<(), Report> { let countries = api::geonames::get_countries().await; println!("Countries: {:#?}", countries); println!("CiteAs API is healthy: {}", api::citeas::is_healthy().await); println!("ORCiD API is healthy: {}", api::orcid::is_healthy().await); println!("ROR API is healthy: {}", api::ror::is_healthy().await); println!("Licenses: {:#?}", api::spdx::Licenses::download().await); // let countries = api::geonames::get_countries().await; // println!("Countries: {:#?}", countries); // println!("CiteAs API is healthy: {}", api::citeas::is_healthy().await); // println!("ORCiD API is healthy: {}", api::orcid::is_healthy().await); // println!("ROR API is healthy: {}", api::ror::is_healthy().await); // println!("Licenses: {:#?}", api::spdx::Licenses::download().await); gitlab_example().await; // orcid_example().await; // ror_example().await; Ok(()) } #[allow(dead_code)] async fn gitlab_example() { let _ = dotenvy::from_filename(".env"); match std::env::var("GITLAB_TOKEN") { | Ok(token) => { let project = "34619"; let group = "24758"; println!("GitLab Token: {}", token); println!("Runners: {:#?}", api::gitlab::runners(&token).await); println!("Runner: {:#?}", api::gitlab::runner(project, &token).await); println!("Groups: {:#?}", api::gitlab::groups(group, &token).await); } | Err(_) => println!("GitLab Token not found in environment variables"), } } #[allow(dead_code)] async fn orcid_example() { let params = vec![ param!( Loading acorn-lib/src/io/api/github.rs +6 −6 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ use serde_with::skip_serializing_none; /// [GitHub]: https://docs.github.com/en/rest #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GithubTreeEntry { pub struct TreeEntry { /// Path of tree entry /// /// The path inside the repository. Used to get content of subdirectories. Loading Loading @@ -42,7 +42,7 @@ pub struct GithubTreeEntry { /// "truncated": false /// } /// ``` /// where `"tree"` is a list of [GithubTreeEntry]. /// where `"tree"` is a list of [TreeEntry]. /// /// ### Example Endpoint /// > `https://api.github.com/repos/jhwohlgemuth/pwsh-prelude/git/trees/master?recursive=1` Loading @@ -52,17 +52,17 @@ pub struct GithubTreeEntry { /// [GitHub]: https://docs.github.com/en/rest /// [documentation]: https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28#get-a-tree #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GithubTreeResponse { pub struct TreeResponse { /// SHA1 of tree pub sha: String, /// URL of associated data API endpoint pub url: String, /// List of [GithubTreeEntry] pub tree: Vec<GithubTreeEntry>, /// List of [TreeEntry] pub tree: Vec<TreeEntry>, /// Whether tree is truncated pub truncated: bool, } impl GithubTreeEntry { impl TreeEntry { /// Get path of tree entry pub fn path(self) -> String { self.path Loading acorn-lib/src/io/api/gitlab.rs +354 −5 Original line number Diff line number Diff line //! Module for interacting with GitLab API //! use crate::io::api::TreeEntryType; // TODO: Add custom field list and query pair type // TODO: Finish modeling necessary API endpoints use crate::io::api::{RemoteResource, Searchable, TreeEntryType, INCLUDED_ENDPOINTS}; use crate::io::config::{RunnerStatus, RunnerType}; use crate::param; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; /// Type for GitLab API descendent groups response pub type GroupsResponse = Vec<GroupDetails>; /// Type for GitLab API all runners response pub type RunnersResponse = Vec<RunnerDetails>; /// Type for GitLab API response for a tree pub type GitlabTreeResponse = Vec<GitlabTreeEntry>; pub type TreeResponse = Vec<TreeEntry>; /// Access level of the runner #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum AccessLevel { /// Not protected NotProtected, /// Ref protected RefProtected, } /// Group visibility level #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum GroupVisibility { /// Public visibility Public, /// Internal visibility Internal, /// Private visibility Private, } /// Runner group details #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GroupDetails { /// Numeric ID of the group #[serde(rename = "id")] pub identifier: u64, /// URL of the group page #[serde(rename = "web_url")] pub url: String, /// Group name pub name: String, /// Group path pub path: Option<String>, /// Group description pub description: Option<String>, /// Whether emails are disabled #[serde(default)] pub emails_disabled: bool, /// Whether emails are enabled #[serde(default)] pub emails_enabled: bool, /// Whether diff previews appear in emails #[serde(default)] pub show_diff_preview_in_email: bool, /// Group visibility level pub visibility: Option<GroupVisibility>, /// Whether sharing with other groups is locked #[serde(default)] pub share_with_group_lock: bool, /// Whether two-factor authentication is required #[serde(default)] pub require_two_factor_authentication: bool, /// Whether LFS is enabled #[serde(default)] pub lfs_enabled: bool, /// Whether the group is archived #[serde(default)] pub archived: bool, /// Duo features enabled flag #[serde(default)] pub duo_features_enabled: bool, /// Duo features lock flag #[serde(default)] pub lock_duo_features_enabled: bool, /// Auto Duo code review enabled flag #[serde(default)] pub auto_duo_code_review_enabled: bool, /// Whether math rendering limits are enabled #[serde(default)] pub math_rendering_limits_enabled: bool, /// Whether math rendering limits are locked #[serde(default)] pub lock_math_rendering_limits_enabled: bool, /// Whether access requests are enabled #[serde(default)] pub request_access_enabled: bool, /// Grace period for two-factor authentication pub two_factor_grace_period: Option<u64>, /// Project creation level pub project_creation_level: Option<String>, /// Auto DevOps enabled flag pub auto_devops_enabled: Option<bool>, /// Subgroup creation level pub subgroup_creation_level: Option<String>, /// Whether mentions are disabled pub mentions_disabled: Option<bool>, /// Default branch name pub default_branch: Option<String>, /// Default branch protection mode pub default_branch_protection: Option<u64>, /// Default branch protection policy details pub default_branch_protection_defaults: Option<RunnerGroupBranchProtectionDefaults>, /// Group avatar URL #[serde(rename = "avatar_url")] pub avatar_url: Option<String>, /// Group full display name pub full_name: Option<String>, /// Group full path pub full_path: Option<String>, /// Group creation timestamp in ISO-8601 format pub created_at: Option<String>, /// Parent group identifier pub parent_id: Option<u64>, /// Organization identifier pub organization_id: Option<u64>, /// Shared runners setting pub shared_runners_setting: Option<String>, /// Maximum artifacts size limit pub max_artifacts_size: Option<u64>, /// Group deletion schedule date pub marked_for_deletion_on: Option<String>, /// LDAP common name pub ldap_cn: Option<String>, /// LDAP access value pub ldap_access: Option<String>, /// File template project identifier pub file_template_project_id: Option<u64>, /// Wiki access level pub wiki_access_level: Option<String>, /// Duo core features enabled flag pub duo_core_features_enabled: Option<bool>, } /// GitLab API response for runner details /// ### Example JSON response /// ```json /// { /// "active": true, /// "paused": false, /// "architecture": null, /// "description": "test-1-20150125", /// "id": 6, /// "ip_address": "", /// "is_shared": false, /// "runner_type": "project_type", /// "contacted_at": "2016-01-25T16:39:48.066Z", /// "maintenance_note": null, /// "name": null, /// "online": true, /// "status": "online", /// "platform": null, /// "projects": [ /// { /// "id": 1, /// "name": "GitLab Community Edition", /// "name_with_namespace": "GitLab.org / GitLab Community Edition", /// "path": "gitlab-foss", /// "path_with_namespace": "gitlab-org/gitlab-foss" /// } /// ], /// "revision": null, /// "tag_list": [ /// "ruby", /// "mysql" /// ], /// "version": null, /// "access_level": "ref_protected", /// "maximum_timeout": 3600 /// } /// ``` #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RunnerDetails { /// Numeric ID of the runner #[serde(rename = "id")] pub identifier: u64, /// Whether the runner is active #[serde(default)] pub active: bool, /// Whether the runner is online #[serde(default)] pub online: bool, /// Whether the runner is paused #[serde(default)] pub paused: bool, /// Whether the runner is shared #[serde(default)] #[serde(rename = "is_shared")] pub shared: bool, /// CPU architecture reported by the runner pub architecture: Option<String>, /// Runner description pub description: Option<String>, /// Runner IP address pub ip_address: Option<String>, /// Type of runner (for example, `project_type`) pub runner_type: Option<RunnerType>, /// Created by user pub created_by: Option<UserDetails>, /// Created timestamp in ISO-8601 format pub created_at: Option<String>, /// Last contact timestamp in ISO-8601 format pub contacted_at: Option<String>, /// Optional maintenance note pub maintenance_note: Option<String>, /// Optional display name pub name: Option<String>, /// Current runner status pub status: Option<RunnerStatus>, /// Current job execution status pub job_execution_status: Option<String>, /// Optional platform string pub platform: Option<String>, /// Projects associated with this runner pub projects: Option<Vec<RunnerScope>>, /// Groups associated with this runner pub groups: Option<Vec<RunnerScope>>, /// Optional Git revision for the runner version pub revision: Option<String>, /// Runner tags #[serde(rename = "tag_list")] pub tags: Option<Vec<String>>, /// Optional runner version pub version: Option<String>, /// Access level for this runner pub access_level: Option<AccessLevel>, /// Maximum timeout in seconds pub maximum_timeout: Option<u64>, } /// Access level entry for branch protection settings #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RunnerGroupAccessLevel { /// Numeric access level pub access_level: u64, } /// Runner group branch protection defaults #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RunnerGroupBranchProtectionDefaults { /// Access levels allowed to push pub allowed_to_push: Vec<RunnerGroupAccessLevel>, /// Whether force push is allowed pub allow_force_push: bool, /// Access levels allowed to merge pub allowed_to_merge: Vec<RunnerGroupAccessLevel>, } /// Runner scope details for project or group entries #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RunnerScope { /// Numeric ID of the scope entry #[serde(rename = "id")] pub identifier: u64, /// Scope name pub name: String, /// Project path pub path: Option<String>, /// Project name including namespace pub name_with_namespace: Option<String>, /// Project path including namespace pub path_with_namespace: Option<String>, /// URL of the group page #[serde(rename = "web_url")] pub url: Option<String>, } /// Struct for GitLab tree entry /// /// See <https://docs.gitlab.com/api/repositories/#list-repository-tree> #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GitlabTreeEntry { pub struct TreeEntry { /// Integer ID of GitLab project /// /// See <https://docs.gitlab.com/api/projects/#get-a-single-project> for more information Loading @@ -23,12 +287,49 @@ pub struct GitlabTreeEntry { pub entry_type: TreeEntryType, /// Path of tree entry /// /// The path inside the repository. Used to get content of subdirectories. /// The path inside the repository Used to get content of subdirectories pub path: String, /// Mode of tree entry pub mode: String, } impl GitlabTreeEntry { /// User details /// ### Example JSON response /// ```json /// { /// "avatar_url": String("https://code.ornl.gov/uploads/-/system/user/avatar/4862/avatar.png"), /// "id": Number(4862), /// "locked": Bool(false), /// "name": String("Wohlgemuth, Jason"), /// "public_email": String("wohlgemuthjh@ornl.gov"), /// "state": String("active"), /// "username": String("o9w"), /// "web_url": String("https://code.ornl.gov/o9w"), /// } /// ``` #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UserDetails { /// URL of user avatar image pub avatar_url: String, /// Numeric ID of user #[serde(rename = "id")] pub identifier: u64, /// Whether the user is locked pub locked: bool, /// User's full name pub name: String, /// User's public email address #[serde(rename = "public_email")] pub email: Option<String>, /// User state (for example, "active") pub state: String, /// Username/handle of the user pub username: String, /// URL of the user's profile page #[serde(rename = "web_url")] pub url: String, } impl TreeEntry { /// Get path of tree entry pub fn path(self) -> String { self.path Loading @@ -39,3 +340,51 @@ impl GitlabTreeEntry { entry_type.eq(&TreeEntryType::Blob) } } /// Get descendant groups of a group by identifier pub async fn groups(identifier: impl Into<String>, token: impl Into<String>) -> Result<GroupsResponse, String> { let name = "GitLab"; let action = "groups"; match INCLUDED_ENDPOINTS.find_by_name(name) { | Some(endpoint) => { let params = vec![ param!(Header, "PRIVATE-TOKEN", &token.into()), param!(TemplateValue, "identifier", &identifier.into()), // param!(FieldList, "page", "1"), param!(FieldList, "per_page", "100"), ]; let response = endpoint.invoke(action, Some(params)).await; endpoint.handle::<GroupsResponse>(response) } | None => Err(format!("{name} API endpoint not found")), } } /// Get runner details by identifier pub async fn runner(identifier: impl Into<String>, token: impl Into<String>) -> Result<RunnerDetails, String> { let name = "GitLab"; let action = "runners"; match INCLUDED_ENDPOINTS.find_by_name(name) { | Some(endpoint) => { let params = vec![ param!(Header, "PRIVATE-TOKEN", &token.into()), param!(TemplateValue, "identifier", &identifier.into()), ]; let response = endpoint.invoke(action, Some(params)).await; dbg!(&response); endpoint.handle::<RunnerDetails>(response) } | None => Err(format!("{name} API endpoint not found")), } } /// Get runners visible to user associated with given token pub async fn runners(token: impl Into<String>) -> Result<RunnersResponse, String> { let name = "GitLab"; let action = "runners"; match INCLUDED_ENDPOINTS.find_by_name(name) { | Some(endpoint) => { let params = vec![param!(Header, "PRIVATE-TOKEN", &token.into())]; let response = endpoint.invoke(action, Some(params)).await; endpoint.handle::<RunnersResponse>(response) } | None => Err(format!("{name} API endpoint not found")), } } acorn-lib/src/io/config.rs +24 −3 Original line number Diff line number Diff line Loading @@ -4,8 +4,8 @@ //! //! The ACORN configuration file configures what buckets should be downloaded, readability analysis, <span title="Large Language Model">LLM</span> settings, and more. //! use crate::io::api::github::{GithubTreeEntry, GithubTreeResponse}; use crate::io::api::gitlab::{GitlabTreeEntry, GitlabTreeResponse}; use crate::io::api::github::{TreeEntry as GithubTreeEntry, TreeResponse as GithubTreeResponse}; use crate::io::api::gitlab::{TreeEntry as GitlabTreeEntry, TreeResponse as GitlabTreeResponse}; use crate::io::api::Endpoint; use crate::io::{files_all, get, parent, read_file, write_file, FromPath, InputOutput}; use crate::prelude::{self, create_dir_all, exit, io, Cursor, Error, File, PathBuf}; Loading @@ -25,22 +25,43 @@ use serde_with::skip_serializing_none; use tracing::{debug, error, trace, warn}; const IGNORE: [&str; 5] = [".gitignore", ".gitlab-ci.yml", ".gitkeep", ".DS_Store", "README.md"]; /// Runner status for CI/CD pipelines #[derive(Clone, Debug, Default, Display, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RunnerStatus { /// Online and available to run jobs #[default] Online, /// Offline and/or unavailable to run jobs Offline, /// Runner has not contacted the server for a while Stale, /// Runner has never contacted the server NeverContacted, /// Deprecated Active, /// Deprecated Paused, } /// Runner types for CI/CD pipelines /// /// Mostly for GitLab as GitHub only has two types: hosted (by GitHub) and self-hosted #[derive(Clone, Debug, Default, Display, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum RunnerType { /// Accessible to a specific group and its projects/subgroups #[default] #[display("group_type")] #[serde(rename = "group_type", alias = "group")] Group, /// Available to all projects and groups within an instance /// > Also called "shared runners" in GitLab #[display("instance_type")] #[serde(rename = "instance_type", alias = "instance")] Instance, /// Available to a specific project #[display("project_type")] #[serde(rename = "project_type", alias = "project")] Project, } /// Struct for application configuration Loading acorn-lib/src/io/tests/mod.rs +2 −2 Original line number Diff line number Diff line use crate::io::api::gitlab::GitlabTreeEntry; use crate::io::api::gitlab::TreeEntry; use crate::io::api::TreeEntryType; use crate::io::bagit::{Bag, BagInfo, Save}; use crate::io::config::{ApplicationConfiguration, Bucket}; Loading Loading @@ -132,7 +132,7 @@ fn test_bucket() { } #[test] fn test_gitlab_tree_entry() { let entry = GitlabTreeEntry { let entry = TreeEntry { id: 1234.to_string(), name: "acorn".to_string(), entry_type: TreeEntryType::Tree, Loading Loading
acorn-cli/src/commands/gather/mod.rs +23 −7 Original line number Diff line number Diff line use acorn::io::api; use acorn::io::api::{self}; use acorn::param; use acorn::prelude::PathBuf; use clap_verbosity_flag::Verbosity; use color_eyre::eyre::{Report, Result}; pub async fn run(_path: &Option<PathBuf>, _ignore: &Option<String>, _offline: &bool, _verbose: &Verbosity) -> Result<(), Report> { let countries = api::geonames::get_countries().await; println!("Countries: {:#?}", countries); println!("CiteAs API is healthy: {}", api::citeas::is_healthy().await); println!("ORCiD API is healthy: {}", api::orcid::is_healthy().await); println!("ROR API is healthy: {}", api::ror::is_healthy().await); println!("Licenses: {:#?}", api::spdx::Licenses::download().await); // let countries = api::geonames::get_countries().await; // println!("Countries: {:#?}", countries); // println!("CiteAs API is healthy: {}", api::citeas::is_healthy().await); // println!("ORCiD API is healthy: {}", api::orcid::is_healthy().await); // println!("ROR API is healthy: {}", api::ror::is_healthy().await); // println!("Licenses: {:#?}", api::spdx::Licenses::download().await); gitlab_example().await; // orcid_example().await; // ror_example().await; Ok(()) } #[allow(dead_code)] async fn gitlab_example() { let _ = dotenvy::from_filename(".env"); match std::env::var("GITLAB_TOKEN") { | Ok(token) => { let project = "34619"; let group = "24758"; println!("GitLab Token: {}", token); println!("Runners: {:#?}", api::gitlab::runners(&token).await); println!("Runner: {:#?}", api::gitlab::runner(project, &token).await); println!("Groups: {:#?}", api::gitlab::groups(group, &token).await); } | Err(_) => println!("GitLab Token not found in environment variables"), } } #[allow(dead_code)] async fn orcid_example() { let params = vec![ param!( Loading
acorn-lib/src/io/api/github.rs +6 −6 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ use serde_with::skip_serializing_none; /// [GitHub]: https://docs.github.com/en/rest #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GithubTreeEntry { pub struct TreeEntry { /// Path of tree entry /// /// The path inside the repository. Used to get content of subdirectories. Loading Loading @@ -42,7 +42,7 @@ pub struct GithubTreeEntry { /// "truncated": false /// } /// ``` /// where `"tree"` is a list of [GithubTreeEntry]. /// where `"tree"` is a list of [TreeEntry]. /// /// ### Example Endpoint /// > `https://api.github.com/repos/jhwohlgemuth/pwsh-prelude/git/trees/master?recursive=1` Loading @@ -52,17 +52,17 @@ pub struct GithubTreeEntry { /// [GitHub]: https://docs.github.com/en/rest /// [documentation]: https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28#get-a-tree #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GithubTreeResponse { pub struct TreeResponse { /// SHA1 of tree pub sha: String, /// URL of associated data API endpoint pub url: String, /// List of [GithubTreeEntry] pub tree: Vec<GithubTreeEntry>, /// List of [TreeEntry] pub tree: Vec<TreeEntry>, /// Whether tree is truncated pub truncated: bool, } impl GithubTreeEntry { impl TreeEntry { /// Get path of tree entry pub fn path(self) -> String { self.path Loading
acorn-lib/src/io/api/gitlab.rs +354 −5 Original line number Diff line number Diff line //! Module for interacting with GitLab API //! use crate::io::api::TreeEntryType; // TODO: Add custom field list and query pair type // TODO: Finish modeling necessary API endpoints use crate::io::api::{RemoteResource, Searchable, TreeEntryType, INCLUDED_ENDPOINTS}; use crate::io::config::{RunnerStatus, RunnerType}; use crate::param; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; /// Type for GitLab API descendent groups response pub type GroupsResponse = Vec<GroupDetails>; /// Type for GitLab API all runners response pub type RunnersResponse = Vec<RunnerDetails>; /// Type for GitLab API response for a tree pub type GitlabTreeResponse = Vec<GitlabTreeEntry>; pub type TreeResponse = Vec<TreeEntry>; /// Access level of the runner #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum AccessLevel { /// Not protected NotProtected, /// Ref protected RefProtected, } /// Group visibility level #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum GroupVisibility { /// Public visibility Public, /// Internal visibility Internal, /// Private visibility Private, } /// Runner group details #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GroupDetails { /// Numeric ID of the group #[serde(rename = "id")] pub identifier: u64, /// URL of the group page #[serde(rename = "web_url")] pub url: String, /// Group name pub name: String, /// Group path pub path: Option<String>, /// Group description pub description: Option<String>, /// Whether emails are disabled #[serde(default)] pub emails_disabled: bool, /// Whether emails are enabled #[serde(default)] pub emails_enabled: bool, /// Whether diff previews appear in emails #[serde(default)] pub show_diff_preview_in_email: bool, /// Group visibility level pub visibility: Option<GroupVisibility>, /// Whether sharing with other groups is locked #[serde(default)] pub share_with_group_lock: bool, /// Whether two-factor authentication is required #[serde(default)] pub require_two_factor_authentication: bool, /// Whether LFS is enabled #[serde(default)] pub lfs_enabled: bool, /// Whether the group is archived #[serde(default)] pub archived: bool, /// Duo features enabled flag #[serde(default)] pub duo_features_enabled: bool, /// Duo features lock flag #[serde(default)] pub lock_duo_features_enabled: bool, /// Auto Duo code review enabled flag #[serde(default)] pub auto_duo_code_review_enabled: bool, /// Whether math rendering limits are enabled #[serde(default)] pub math_rendering_limits_enabled: bool, /// Whether math rendering limits are locked #[serde(default)] pub lock_math_rendering_limits_enabled: bool, /// Whether access requests are enabled #[serde(default)] pub request_access_enabled: bool, /// Grace period for two-factor authentication pub two_factor_grace_period: Option<u64>, /// Project creation level pub project_creation_level: Option<String>, /// Auto DevOps enabled flag pub auto_devops_enabled: Option<bool>, /// Subgroup creation level pub subgroup_creation_level: Option<String>, /// Whether mentions are disabled pub mentions_disabled: Option<bool>, /// Default branch name pub default_branch: Option<String>, /// Default branch protection mode pub default_branch_protection: Option<u64>, /// Default branch protection policy details pub default_branch_protection_defaults: Option<RunnerGroupBranchProtectionDefaults>, /// Group avatar URL #[serde(rename = "avatar_url")] pub avatar_url: Option<String>, /// Group full display name pub full_name: Option<String>, /// Group full path pub full_path: Option<String>, /// Group creation timestamp in ISO-8601 format pub created_at: Option<String>, /// Parent group identifier pub parent_id: Option<u64>, /// Organization identifier pub organization_id: Option<u64>, /// Shared runners setting pub shared_runners_setting: Option<String>, /// Maximum artifacts size limit pub max_artifacts_size: Option<u64>, /// Group deletion schedule date pub marked_for_deletion_on: Option<String>, /// LDAP common name pub ldap_cn: Option<String>, /// LDAP access value pub ldap_access: Option<String>, /// File template project identifier pub file_template_project_id: Option<u64>, /// Wiki access level pub wiki_access_level: Option<String>, /// Duo core features enabled flag pub duo_core_features_enabled: Option<bool>, } /// GitLab API response for runner details /// ### Example JSON response /// ```json /// { /// "active": true, /// "paused": false, /// "architecture": null, /// "description": "test-1-20150125", /// "id": 6, /// "ip_address": "", /// "is_shared": false, /// "runner_type": "project_type", /// "contacted_at": "2016-01-25T16:39:48.066Z", /// "maintenance_note": null, /// "name": null, /// "online": true, /// "status": "online", /// "platform": null, /// "projects": [ /// { /// "id": 1, /// "name": "GitLab Community Edition", /// "name_with_namespace": "GitLab.org / GitLab Community Edition", /// "path": "gitlab-foss", /// "path_with_namespace": "gitlab-org/gitlab-foss" /// } /// ], /// "revision": null, /// "tag_list": [ /// "ruby", /// "mysql" /// ], /// "version": null, /// "access_level": "ref_protected", /// "maximum_timeout": 3600 /// } /// ``` #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RunnerDetails { /// Numeric ID of the runner #[serde(rename = "id")] pub identifier: u64, /// Whether the runner is active #[serde(default)] pub active: bool, /// Whether the runner is online #[serde(default)] pub online: bool, /// Whether the runner is paused #[serde(default)] pub paused: bool, /// Whether the runner is shared #[serde(default)] #[serde(rename = "is_shared")] pub shared: bool, /// CPU architecture reported by the runner pub architecture: Option<String>, /// Runner description pub description: Option<String>, /// Runner IP address pub ip_address: Option<String>, /// Type of runner (for example, `project_type`) pub runner_type: Option<RunnerType>, /// Created by user pub created_by: Option<UserDetails>, /// Created timestamp in ISO-8601 format pub created_at: Option<String>, /// Last contact timestamp in ISO-8601 format pub contacted_at: Option<String>, /// Optional maintenance note pub maintenance_note: Option<String>, /// Optional display name pub name: Option<String>, /// Current runner status pub status: Option<RunnerStatus>, /// Current job execution status pub job_execution_status: Option<String>, /// Optional platform string pub platform: Option<String>, /// Projects associated with this runner pub projects: Option<Vec<RunnerScope>>, /// Groups associated with this runner pub groups: Option<Vec<RunnerScope>>, /// Optional Git revision for the runner version pub revision: Option<String>, /// Runner tags #[serde(rename = "tag_list")] pub tags: Option<Vec<String>>, /// Optional runner version pub version: Option<String>, /// Access level for this runner pub access_level: Option<AccessLevel>, /// Maximum timeout in seconds pub maximum_timeout: Option<u64>, } /// Access level entry for branch protection settings #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RunnerGroupAccessLevel { /// Numeric access level pub access_level: u64, } /// Runner group branch protection defaults #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RunnerGroupBranchProtectionDefaults { /// Access levels allowed to push pub allowed_to_push: Vec<RunnerGroupAccessLevel>, /// Whether force push is allowed pub allow_force_push: bool, /// Access levels allowed to merge pub allowed_to_merge: Vec<RunnerGroupAccessLevel>, } /// Runner scope details for project or group entries #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RunnerScope { /// Numeric ID of the scope entry #[serde(rename = "id")] pub identifier: u64, /// Scope name pub name: String, /// Project path pub path: Option<String>, /// Project name including namespace pub name_with_namespace: Option<String>, /// Project path including namespace pub path_with_namespace: Option<String>, /// URL of the group page #[serde(rename = "web_url")] pub url: Option<String>, } /// Struct for GitLab tree entry /// /// See <https://docs.gitlab.com/api/repositories/#list-repository-tree> #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GitlabTreeEntry { pub struct TreeEntry { /// Integer ID of GitLab project /// /// See <https://docs.gitlab.com/api/projects/#get-a-single-project> for more information Loading @@ -23,12 +287,49 @@ pub struct GitlabTreeEntry { pub entry_type: TreeEntryType, /// Path of tree entry /// /// The path inside the repository. Used to get content of subdirectories. /// The path inside the repository Used to get content of subdirectories pub path: String, /// Mode of tree entry pub mode: String, } impl GitlabTreeEntry { /// User details /// ### Example JSON response /// ```json /// { /// "avatar_url": String("https://code.ornl.gov/uploads/-/system/user/avatar/4862/avatar.png"), /// "id": Number(4862), /// "locked": Bool(false), /// "name": String("Wohlgemuth, Jason"), /// "public_email": String("wohlgemuthjh@ornl.gov"), /// "state": String("active"), /// "username": String("o9w"), /// "web_url": String("https://code.ornl.gov/o9w"), /// } /// ``` #[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UserDetails { /// URL of user avatar image pub avatar_url: String, /// Numeric ID of user #[serde(rename = "id")] pub identifier: u64, /// Whether the user is locked pub locked: bool, /// User's full name pub name: String, /// User's public email address #[serde(rename = "public_email")] pub email: Option<String>, /// User state (for example, "active") pub state: String, /// Username/handle of the user pub username: String, /// URL of the user's profile page #[serde(rename = "web_url")] pub url: String, } impl TreeEntry { /// Get path of tree entry pub fn path(self) -> String { self.path Loading @@ -39,3 +340,51 @@ impl GitlabTreeEntry { entry_type.eq(&TreeEntryType::Blob) } } /// Get descendant groups of a group by identifier pub async fn groups(identifier: impl Into<String>, token: impl Into<String>) -> Result<GroupsResponse, String> { let name = "GitLab"; let action = "groups"; match INCLUDED_ENDPOINTS.find_by_name(name) { | Some(endpoint) => { let params = vec![ param!(Header, "PRIVATE-TOKEN", &token.into()), param!(TemplateValue, "identifier", &identifier.into()), // param!(FieldList, "page", "1"), param!(FieldList, "per_page", "100"), ]; let response = endpoint.invoke(action, Some(params)).await; endpoint.handle::<GroupsResponse>(response) } | None => Err(format!("{name} API endpoint not found")), } } /// Get runner details by identifier pub async fn runner(identifier: impl Into<String>, token: impl Into<String>) -> Result<RunnerDetails, String> { let name = "GitLab"; let action = "runners"; match INCLUDED_ENDPOINTS.find_by_name(name) { | Some(endpoint) => { let params = vec![ param!(Header, "PRIVATE-TOKEN", &token.into()), param!(TemplateValue, "identifier", &identifier.into()), ]; let response = endpoint.invoke(action, Some(params)).await; dbg!(&response); endpoint.handle::<RunnerDetails>(response) } | None => Err(format!("{name} API endpoint not found")), } } /// Get runners visible to user associated with given token pub async fn runners(token: impl Into<String>) -> Result<RunnersResponse, String> { let name = "GitLab"; let action = "runners"; match INCLUDED_ENDPOINTS.find_by_name(name) { | Some(endpoint) => { let params = vec![param!(Header, "PRIVATE-TOKEN", &token.into())]; let response = endpoint.invoke(action, Some(params)).await; endpoint.handle::<RunnersResponse>(response) } | None => Err(format!("{name} API endpoint not found")), } }
acorn-lib/src/io/config.rs +24 −3 Original line number Diff line number Diff line Loading @@ -4,8 +4,8 @@ //! //! The ACORN configuration file configures what buckets should be downloaded, readability analysis, <span title="Large Language Model">LLM</span> settings, and more. //! use crate::io::api::github::{GithubTreeEntry, GithubTreeResponse}; use crate::io::api::gitlab::{GitlabTreeEntry, GitlabTreeResponse}; use crate::io::api::github::{TreeEntry as GithubTreeEntry, TreeResponse as GithubTreeResponse}; use crate::io::api::gitlab::{TreeEntry as GitlabTreeEntry, TreeResponse as GitlabTreeResponse}; use crate::io::api::Endpoint; use crate::io::{files_all, get, parent, read_file, write_file, FromPath, InputOutput}; use crate::prelude::{self, create_dir_all, exit, io, Cursor, Error, File, PathBuf}; Loading @@ -25,22 +25,43 @@ use serde_with::skip_serializing_none; use tracing::{debug, error, trace, warn}; const IGNORE: [&str; 5] = [".gitignore", ".gitlab-ci.yml", ".gitkeep", ".DS_Store", "README.md"]; /// Runner status for CI/CD pipelines #[derive(Clone, Debug, Default, Display, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RunnerStatus { /// Online and available to run jobs #[default] Online, /// Offline and/or unavailable to run jobs Offline, /// Runner has not contacted the server for a while Stale, /// Runner has never contacted the server NeverContacted, /// Deprecated Active, /// Deprecated Paused, } /// Runner types for CI/CD pipelines /// /// Mostly for GitLab as GitHub only has two types: hosted (by GitHub) and self-hosted #[derive(Clone, Debug, Default, Display, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum RunnerType { /// Accessible to a specific group and its projects/subgroups #[default] #[display("group_type")] #[serde(rename = "group_type", alias = "group")] Group, /// Available to all projects and groups within an instance /// > Also called "shared runners" in GitLab #[display("instance_type")] #[serde(rename = "instance_type", alias = "instance")] Instance, /// Available to a specific project #[display("project_type")] #[serde(rename = "project_type", alias = "project")] Project, } /// Struct for application configuration Loading
acorn-lib/src/io/tests/mod.rs +2 −2 Original line number Diff line number Diff line use crate::io::api::gitlab::GitlabTreeEntry; use crate::io::api::gitlab::TreeEntry; use crate::io::api::TreeEntryType; use crate::io::bagit::{Bag, BagInfo, Save}; use crate::io::config::{ApplicationConfiguration, Bucket}; Loading Loading @@ -132,7 +132,7 @@ fn test_bucket() { } #[test] fn test_gitlab_tree_entry() { let entry = GitlabTreeEntry { let entry = TreeEntry { id: 1234.to_string(), name: "acorn".to_string(), entry_type: TreeEntryType::Tree, Loading