@@ -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
@@ -70,18 +75,38 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
7075
7176 for repository in & repositories {
7277 let ( project_name, repository_name) = repository. name . split_once ( '/' ) . unwrap ( ) ;
73- if project_name == "sandbox" {
78+ if project_name == "sandbox"
79+ && ( repository_name != "git-sync" && repository_name != "ubi8-rust-builder" )
80+ {
7481 continue ;
7582 }
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 ?;
83+
84+ let mut attestations: Vec < String > = Vec :: with_capacity ( 32 ) ;
85+ let mut potentially_attested_artifacts: Vec < & str > = Vec :: with_capacity ( 32 ) ;
86+ let mut artifacts: Vec < Artifact > = Vec :: with_capacity ( 64 ) ;
87+ let mut page = 1 ;
88+ let page_size = 20 ;
89+ loop {
90+ let artifacts_page: Vec < Artifact > = reqwest:: get ( format ! (
91+ "{}/projects/{}/repositories/{}/artifacts?page_size={}&page={}" ,
92+ base_url,
93+ encode( project_name) ,
94+ encode( repository_name) ,
95+ page_size,
96+ page
97+ ) )
98+ . await ?
99+ . json ( )
100+ . await ?;
101+
102+ let number_of_returned_artifacts = artifacts_page. len ( ) ;
103+ artifacts. extend ( artifacts_page) ;
104+ if number_of_returned_artifacts < page_size {
105+ break ;
106+ }
107+ page += 1 ;
108+ }
109+
85110 for artifact in & artifacts {
86111 if artifact
87112 . tags
@@ -99,15 +124,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
99124 {
100125 // it's an attestation, attestations artifacts themselves are not signed
101126 println ! (
102- "skipping attestation {}{} ({})" ,
127+ "skipping attestation {} {} ({})" ,
103128 repository_name,
104129 artifact. digest,
105130 artifact. tags. as_ref( ) . unwrap( )
106131 ) ;
132+ attestations. push ( artifact. tags . as_ref ( ) . unwrap ( ) [ 0 ] . name . clone ( ) ) ;
107133 continue ;
134+ } else {
135+ potentially_attested_artifacts. push ( & artifact. digest [ 7 ..71 ] ) ;
108136 }
109137
110- if project_name == "sdp" && ( repository_name != "git-sync" && repository_name != "ubi8-rust-builder" ) {
138+ if project_name == "sdp"
139+ && ( repository_name != "git-sync" && repository_name != "ubi8-rust-builder" )
140+ {
111141 if artifact. manifest_media_type
112142 != "application/vnd.docker.distribution.manifest.v2+json"
113143 {
@@ -159,6 +189,24 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
159189 exit ( cmd_output. status . code ( ) . unwrap_or ( 1 ) ) ;
160190 }
161191 }
192+
193+ // remove dangling attestations
194+ for attestation in attestations {
195+ if !potentially_attested_artifacts. contains ( & & attestation[ 7 ..71 ] ) {
196+ println ! ( "removing dangling attestation {}" , attestation) ;
197+ reqwest:: Client :: new ( )
198+ . delete ( format ! (
199+ "{}/projects/{}/repositories/{}/artifacts/{}" ,
200+ base_url,
201+ encode( project_name) ,
202+ encode( repository_name) ,
203+ attestation
204+ ) )
205+ . basic_auth ( "robot$stackable-cleanup" , Some ( "XX" ) )
206+ . send ( )
207+ . await ?;
208+ }
209+ }
162210 }
163211
164212 if repositories. len ( ) < page_size {
@@ -168,5 +216,51 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
168216 page += 1 ;
169217 }
170218
219+ let latest_rust_builder_artifact: Artifact = reqwest:: Client :: new ( )
220+ . get ( format ! (
221+ "{}/projects/sdp/repositories/ubi8-rust-builder/artifacts/latest" ,
222+ base_url,
223+ ) )
224+ . send ( )
225+ . await ?
226+ . json ( )
227+ . await ?;
228+
229+ let referenced_digests = latest_rust_builder_artifact
230+ . references
231+ . unwrap ( )
232+ . into_iter ( )
233+ . map ( |reference| reference. child_digest )
234+ . collect :: < Vec < String > > ( ) ;
235+
236+ let rust_builder_artifacts: Vec < Artifact > = reqwest:: get ( format ! (
237+ "{}/projects/sdp/repositories/ubi8-rust-builder/artifacts?page_size=100" ,
238+ base_url
239+ ) )
240+ . await ?
241+ . json ( )
242+ . await ?;
243+
244+ // keep "latest" and its referenced artifacts
245+ for artifact in rust_builder_artifacts {
246+ if artifact. digest != latest_rust_builder_artifact. digest
247+ && !referenced_digests. contains ( & artifact. digest )
248+ {
249+ println ! (
250+ "removing dangling rust builder artifact {}" ,
251+ artifact. digest
252+ ) ;
253+ reqwest:: Client :: new ( )
254+ . delete ( format ! (
255+ "{}/projects/sdp/repositories/ubi8-rust-builder/artifacts/{}" ,
256+ base_url, artifact. digest
257+ ) )
258+ . basic_auth ( "robot$stackable-cleanup" , Some ( "XX" ) )
259+ . send ( )
260+ . await ?;
261+ }
262+ }
263+
171264 Ok ( ( ) )
172265}
266+ // cachebust
0 commit comments