@@ -43,19 +43,24 @@ impl std::ops::Deref for TagList {
4343 }
4444}
4545
46+ #[ derive( Deserialize , Debug ) ]
47+ struct ArtifactReference {
48+ child_digest : String ,
49+ }
4650#[ derive( Deserialize , Debug ) ]
4751struct Artifact {
4852 digest : String ,
4953 manifest_media_type : String ,
5054 media_type : String ,
5155 tags : Option < TagList > ,
56+ references : Option < Vec < ArtifactReference > > ,
5257}
5358
5459#[ tokio:: main]
5560async fn main ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
5661 let registry_hostname = "oci.stackable.tech" ;
5762 let base_url = format ! ( "https://{}/api/v2.0" , registry_hostname) ;
58- let page_size = 10 ;
63+ let page_size = 20 ;
5964 let mut page = 1 ;
6065 let attestation_tag_regex = Regex :: new ( r"^sha256-[0-9a-f]{64}.att$" ) . unwrap ( ) ;
6166
@@ -73,15 +78,33 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
7378 if project_name == "sandbox" {
7479 continue ;
7580 }
76- let artifacts: Vec < Artifact > = reqwest:: get ( format ! (
77- "{}/projects/{}/repositories/{}/artifacts" ,
78- base_url,
79- encode( project_name) ,
80- encode( repository_name)
81- ) )
82- . await ?
83- . json ( )
84- . await ?;
81+
82+ let mut attestations: Vec < String > = Vec :: with_capacity ( 32 ) ;
83+ let mut potentially_attested_artifacts: Vec < & str > = Vec :: with_capacity ( 32 ) ;
84+ let mut artifacts: Vec < Artifact > = Vec :: with_capacity ( 64 ) ;
85+ let mut page = 1 ;
86+ let page_size = 20 ;
87+ loop {
88+ let artifacts_page: Vec < Artifact > = reqwest:: get ( format ! (
89+ "{}/projects/{}/repositories/{}/artifacts?page_size={}&page={}" ,
90+ base_url,
91+ encode( project_name) ,
92+ encode( repository_name) ,
93+ page_size,
94+ page
95+ ) )
96+ . await ?
97+ . json ( )
98+ . await ?;
99+
100+ let number_of_returned_artifacts = artifacts_page. len ( ) ;
101+ artifacts. extend ( artifacts_page) ;
102+ if number_of_returned_artifacts < page_size {
103+ break ;
104+ }
105+ page += 1 ;
106+ }
107+
85108 for artifact in & artifacts {
86109 if artifact
87110 . tags
@@ -99,15 +122,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
99122 {
100123 // it's an attestation, attestations artifacts themselves are not signed
101124 println ! (
102- "skipping attestation {}{} ({})" ,
125+ "skipping attestation {} {} ({})" ,
103126 repository_name,
104127 artifact. digest,
105128 artifact. tags. as_ref( ) . unwrap( )
106129 ) ;
130+ attestations. push ( artifact. tags . as_ref ( ) . unwrap ( ) [ 0 ] . name . clone ( ) ) ;
107131 continue ;
132+ } else {
133+ potentially_attested_artifacts. push ( & artifact. digest [ 7 ..71 ] ) ;
108134 }
109135
110- if project_name == "sdp" && ( repository_name != "git-sync" && repository_name != "ubi8-rust-builder" ) {
136+ if project_name == "sdp"
137+ && ( repository_name != "git-sync" && repository_name != "ubi8-rust-builder" )
138+ {
111139 if artifact. manifest_media_type
112140 != "application/vnd.docker.distribution.manifest.v2+json"
113141 {
@@ -159,6 +187,24 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
159187 exit ( cmd_output. status . code ( ) . unwrap_or ( 1 ) ) ;
160188 }
161189 }
190+
191+ // remove dangling attestations
192+ for attestation in attestations {
193+ if !potentially_attested_artifacts. contains ( & & attestation[ 7 ..71 ] ) {
194+ println ! ( "removing dangling attestation {}" , attestation) ;
195+ reqwest:: Client :: new ( )
196+ . delete ( format ! (
197+ "{}/projects/{}/repositories/{}/artifacts/{}" ,
198+ base_url,
199+ encode( project_name) ,
200+ encode( repository_name) ,
201+ attestation
202+ ) )
203+ . basic_auth ( "robot$stackable-cleanup" , Some ( "XX" ) )
204+ . send ( )
205+ . await ?;
206+ }
207+ }
162208 }
163209
164210 if repositories. len ( ) < page_size {
@@ -168,5 +214,51 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
168214 page += 1 ;
169215 }
170216
217+ let latest_rust_builder_artifact: Artifact = reqwest:: Client :: new ( )
218+ . get ( format ! (
219+ "{}/projects/sdp/repositories/ubi8-rust-builder/artifacts/latest" ,
220+ base_url,
221+ ) )
222+ . send ( )
223+ . await ?
224+ . json ( )
225+ . await ?;
226+
227+ let referenced_digests = latest_rust_builder_artifact
228+ . references
229+ . unwrap ( )
230+ . into_iter ( )
231+ . map ( |reference| reference. child_digest )
232+ . collect :: < Vec < String > > ( ) ;
233+
234+ let rust_builder_artifacts: Vec < Artifact > = reqwest:: get ( format ! (
235+ "{}/projects/sdp/repositories/ubi8-rust-builder/artifacts?page_size=100" ,
236+ base_url
237+ ) )
238+ . await ?
239+ . json ( )
240+ . await ?;
241+
242+ // keep "latest" and its referenced artifacts
243+ for artifact in rust_builder_artifacts {
244+ if artifact. digest != latest_rust_builder_artifact. digest
245+ && !referenced_digests. contains ( & artifact. digest )
246+ {
247+ println ! (
248+ "removing dangling rust builder artifact {}" ,
249+ artifact. digest
250+ ) ;
251+ reqwest:: Client :: new ( )
252+ . delete ( format ! (
253+ "{}/projects/sdp/repositories/ubi8-rust-builder/artifacts/{}" ,
254+ base_url, artifact. digest
255+ ) )
256+ . basic_auth ( "robot$stackable-cleanup" , Some ( "XX" ) )
257+ . send ( )
258+ . await ?;
259+ }
260+ }
261+
171262 Ok ( ( ) )
172263}
264+ // cachebust
0 commit comments