g1t/crates/contracts/src/packages.rs

282 lines9,059 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member1//! The packages service: the registries a workspace publishes to and
2//! installs from, beside its code (docs/PACKAGES.md). Container images
3//! first, spoken over the OCI Distribution protocol on `g1t.sh/v2/`; npm,
4//! Composer, Cargo and Go after.
5//!
6//! The site reaches it over `POST /rpc/<method>` with the arguments below;
7//! the registries' own protocols are any other request. Mirrors
8//! `packages/contracts/src/packages.ts`.
9//!
10//! Who may do what: a package linked to a repository has that
11//! repository's visibility and roles (Read pulls, Write publishes, Admin
12//! deletes and changes settings). An unlinked one belongs to its workspace:
13//! members by the base permission, owners delete. Public packages pull
14//! anonymously.
15
16use serde::{Deserialize, Serialize};
17
18use crate::audit::Surface;
19use crate::{User, Viewer};
20
21/// Which registry a package is in.
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum Ecosystem {
25 Container,
26 Npm,
27 Composer,
28 Cargo,
29 Go,
30}
31
32impl Ecosystem {
33 pub const ALL: [Ecosystem; 5] = [
34 Ecosystem::Container,
35 Ecosystem::Npm,
36 Ecosystem::Composer,
37 Ecosystem::Cargo,
38 Ecosystem::Go,
39 ];
40
41 pub fn as_str(self) -> &'static str {
42 match self {
43 Ecosystem::Container => "container",
44 Ecosystem::Npm => "npm",
45 Ecosystem::Composer => "composer",
46 Ecosystem::Cargo => "cargo",
47 Ecosystem::Go => "go",
48 }
49 }
50
51 pub fn parse(text: &str) -> Option<Ecosystem> {
52 Ecosystem::ALL.into_iter().find(|e| e.as_str() == text)
53 }
54}
55
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "snake_case")]
58pub enum Visibility {
59 Public,
60 #[default]
61 Private,
62}
63
64impl Visibility {
65 pub fn as_str(self) -> &'static str {
66 match self {
67 Visibility::Public => "public",
68 Visibility::Private => "private",
69 }
70 }
71
72 pub fn parse(text: &str) -> Visibility {
73 if text == "public" { Visibility::Public } else { Visibility::Private }
74 }
75}
76
77/// The repository a package is linked to.
78#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
79pub struct LinkedRepo {
80 pub id: String,
81 pub namespace: String,
82 pub name: String,
83}
84
85/// A package as listings show it.
86#[derive(Clone, Debug, Serialize, Deserialize)]
87pub struct PackageSummary {
88 pub id: String,
89 pub workspace: String,
90 pub ecosystem: Ecosystem,
91 /// Without the workspace: `web` for `g1t.sh/acme/web`.
92 pub name: String,
93 /// What a client is given: `g1t.sh/acme/web` for a container image.
94 pub address: String,
95 /// Linked packages follow their repository's visibility.
96 pub visibility: Visibility,
97 pub repo: Option<LinkedRepo>,
98 pub description: Option<String>,
99 pub versions: u32,
100 /// The newest version's tag (for a container image, `latest` when it
101 /// has one) or version.
102 pub latest: Option<String>,
103 /// Bytes its versions hold, each file counted once.
104 pub size: u64,
105 /// Pulls and installs, counted approximately.
106 pub downloads: u64,
107 pub created_at: String,
108 pub updated_at: String,
109}
110
111/// One version: for a container image, one manifest, by digest.
112#[derive(Clone, Debug, Serialize, Deserialize)]
113pub struct PackageVersion {
114 pub id: String,
115 /// A tag, semver or (for container images) the manifest's digest.
116 pub version: String,
117 pub digest: String,
118 /// Bytes of its files: an image's layers, config and manifest.
119 pub size: u64,
120 pub media_type: Option<String>,
121 /// For an OCI artifact: what it is, such as a signature or an SBOM.
122 pub artifact_type: Option<String>,
123 /// For an artifact attached to another version: that version's digest.
124 pub subject: Option<String>,
125 /// For an image index: the platforms it holds, such as `linux/amd64`.
126 pub platforms: Vec<String>,
127 pub tags: Vec<String>,
128 /// The username that published it.
129 pub published_by: Option<String>,
130 pub published_at: String,
131}
132
133#[derive(Clone, Debug, Serialize, Deserialize)]
134pub struct PackageTag {
135 pub tag: String,
136 pub digest: String,
137 pub updated_at: String,
138}
139
140/// What the viewer may do with a package.
141#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
142pub struct PackagePermissions {
143 pub pull: bool,
144 pub push: bool,
145 pub delete: bool,
146 /// Change its visibility and link.
147 pub admin: bool,
148}
149
150/// `get_package`.
151#[derive(Clone, Debug, Serialize, Deserialize)]
152pub struct PackageDetail {
153 pub package: PackageSummary,
154 /// Newest first.
155 pub versions: Vec<PackageVersion>,
156 pub tags: Vec<PackageTag>,
157 pub permissions: PackagePermissions,
158}
159
160/// `list_packages`: the packages in a workspace the viewer may pull, newest
161/// first. Returns `Outcome<Vec<PackageSummary>>`.
162#[derive(Clone, Debug, Default, Serialize, Deserialize)]
163pub struct ListPackagesArgs {
164 pub workspace: String,
165 pub viewer: Viewer,
166 #[serde(default)]
167 pub ecosystem: Option<Ecosystem>,
168 /// Only those linked to this repository.
169 #[serde(default)]
170 pub repo_id: Option<String>,
171 /// Matched against names.
172 #[serde(default)]
173 pub query: Option<String>,
174}
175
176/// `get_package`. Returns `Outcome<PackageDetail>`; not found when the
177/// viewer may not pull it.
178#[derive(Clone, Debug, Serialize, Deserialize)]
179pub struct GetPackageArgs {
180 pub workspace: String,
181 pub ecosystem: Ecosystem,
182 pub name: String,
183 pub viewer: Viewer,
184}
185
186/// `delete_version`: a version, by its version or digest, or by a tag that
187/// points to it. Its tags go with it. Returns `Outcome<()>`.
188#[derive(Clone, Debug, Serialize, Deserialize)]
189pub struct DeleteVersionArgs {
190 pub actor: User,
191 pub workspace: String,
192 pub ecosystem: Ecosystem,
193 pub name: String,
194 pub version: String,
195 #[serde(default)]
196 pub surface: Option<Surface>,
197}
198
199/// `delete_package`: a package and every version. Returns `Outcome<()>`.
200#[derive(Clone, Debug, Serialize, Deserialize)]
201pub struct DeletePackageArgs {
202 pub actor: User,
203 pub workspace: String,
204 pub ecosystem: Ecosystem,
205 pub name: String,
206 #[serde(default)]
207 pub surface: Option<Surface>,
208}
209
210/// `set_package`: change a package's visibility, or the repository it is
211/// linked to. `link` names a repository of its workspace; `unlink` takes
212/// the link away (the package is then the workspace's, and private until
213/// someone makes it public). A linked package's visibility is its
214/// repository's, so `visibility` is refused for one. Needs Admin. Returns
215/// `Outcome<PackageSummary>`.
216#[derive(Clone, Debug, Serialize, Deserialize)]
217pub struct SetPackageArgs {
218 pub actor: User,
219 pub workspace: String,
220 pub ecosystem: Ecosystem,
221 pub name: String,
222 #[serde(default)]
223 pub visibility: Option<Visibility>,
224 #[serde(default)]
225 pub link: Option<String>,
226 #[serde(default)]
227 pub unlink: bool,
228 #[serde(default)]
229 pub surface: Option<Surface>,
230}
231
232/// `storage`: what a workspace's packages hold, each file counted once, as
233/// public when any public package uses it. For billing. Returns
234/// [`PackageStorage`].
235#[derive(Clone, Debug, Serialize, Deserialize)]
236pub struct StorageArgs {
237 pub workspace: String,
238}
239
240#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
241pub struct PackageStorage {
242 pub public_bytes: u64,
243 pub private_bytes: u64,
244}
245
246/// `storage_all`: [`PackageStorage`] for every workspace that has
247/// packages, from one query, for billing's daily measure. Takes `{}`;
248/// returns `Vec<WorkspacePackageStorage>`, by workspace.
249#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
250pub struct WorkspacePackageStorage {
251 pub workspace: String,
252 pub public_bytes: u64,
253 pub private_bytes: u64,
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn ecosystems_read_back() {
262 for ecosystem in Ecosystem::ALL {
263 assert_eq!(Ecosystem::parse(ecosystem.as_str()), Some(ecosystem));
264 assert_eq!(serde_json::to_value(ecosystem).unwrap(), ecosystem.as_str());
265 }
266 assert_eq!(Visibility::parse("public"), Visibility::Public);
267 assert_eq!(Visibility::parse("anything"), Visibility::Private);
268 }
269
270 /// The site's copy, `packages/contracts/src/packages.ts`, names the
271 /// same ecosystems and methods.
272 #[test]
273 fn the_typescript_mirror_names_the_same_ecosystems_and_methods() {
274 let ts = include_str!("../../../packages/contracts/src/packages.ts");
275 for ecosystem in Ecosystem::ALL {
276 assert!(ts.contains(&format!("\"{}\"", ecosystem.as_str())), "{}", ecosystem.as_str());
277 }
278 for method in ["list_packages", "get_package", "delete_version", "delete_package", "set_package", "storage", "storage_all"] {
279 assert!(ts.contains(&format!("\"{method}\"")), "{method}");
280 }
281 }
282}