@@ -11,6 +11,7 @@ use object_store::aws::{AmazonS3, AmazonS3Builder};
11
11
use object_store:: local:: LocalFileSystem ;
12
12
use object_store:: memory:: InMemory ;
13
13
use object_store:: path:: Path ;
14
+ use object_store:: prefix:: PrefixStore ;
14
15
use object_store:: { ClientOptions , ObjectStore , Result } ;
15
16
use secrecy:: { ExposeSecret , SecretString } ;
16
17
use std:: fs;
@@ -20,13 +21,15 @@ const PREFIX_CRATES: &str = "crates";
20
21
const PREFIX_READMES : & str = "readmes" ;
21
22
const DEFAULT_REGION : & str = "us-west-1" ;
22
23
const CONTENT_TYPE_CRATE : & str = "application/gzip" ;
24
+ const CONTENT_TYPE_INDEX : & str = "text/plain" ;
23
25
const CONTENT_TYPE_README : & str = "text/html" ;
24
26
const CACHE_CONTROL_IMMUTABLE : & str = "public,max-age=31536000,immutable" ;
27
+ const CACHE_CONTROL_INDEX : & str = "public,max-age=600" ;
25
28
const CACHE_CONTROL_README : & str = "public,max-age=604800" ;
26
29
27
30
#[ derive( Debug ) ]
28
31
pub enum StorageConfig {
29
- S3 ( S3Config ) ,
32
+ S3 { default : S3Config , index : S3Config } ,
30
33
LocalFileSystem { path : PathBuf } ,
31
34
InMemory ,
32
35
}
@@ -43,16 +46,28 @@ impl StorageConfig {
43
46
pub fn from_environment ( ) -> Self {
44
47
if let Ok ( bucket) = dotenvy:: var ( "S3_BUCKET" ) {
45
48
let region = dotenvy:: var ( "S3_REGION" ) . ok ( ) ;
49
+
50
+ let index_bucket = env ( "S3_INDEX_BUCKET" ) ;
51
+ let index_region = dotenvy:: var ( "S3_INDEX_REGION" ) . ok ( ) ;
52
+
46
53
let access_key = env ( "AWS_ACCESS_KEY" ) ;
47
- let secret_key = env ( "AWS_SECRET_KEY" ) . into ( ) ;
48
- let s3 = S3Config {
54
+ let secret_key: SecretString = env ( "AWS_SECRET_KEY" ) . into ( ) ;
55
+
56
+ let default = S3Config {
49
57
bucket,
50
58
region,
59
+ access_key : access_key. clone ( ) ,
60
+ secret_key : secret_key. clone ( ) ,
61
+ } ;
62
+
63
+ let index = S3Config {
64
+ bucket : index_bucket,
65
+ region : index_region,
51
66
access_key,
52
67
secret_key,
53
68
} ;
54
69
55
- return Self :: S3 ( s3 ) ;
70
+ return Self :: S3 { default , index } ;
56
71
}
57
72
58
73
let current_dir = std:: env:: current_dir ( )
@@ -69,6 +84,9 @@ pub struct Storage {
69
84
store : Box < dyn ObjectStore > ,
70
85
crate_upload_store : Box < dyn ObjectStore > ,
71
86
readme_upload_store : Box < dyn ObjectStore > ,
87
+
88
+ index_store : Box < dyn ObjectStore > ,
89
+ index_upload_store : Box < dyn ObjectStore > ,
72
90
}
73
91
74
92
impl Storage {
@@ -78,48 +96,70 @@ impl Storage {
78
96
79
97
pub fn from_config ( config : & StorageConfig ) -> Self {
80
98
match config {
81
- StorageConfig :: S3 ( s3 ) => {
99
+ StorageConfig :: S3 { default , index } => {
82
100
let options = ClientOptions :: default ( ) ;
83
- let store = build_s3 ( s3 , options) ;
101
+ let store = build_s3 ( default , options) ;
84
102
85
103
let options = client_options ( CONTENT_TYPE_CRATE , CACHE_CONTROL_IMMUTABLE ) ;
86
- let crate_upload_store = build_s3 ( s3 , options) ;
104
+ let crate_upload_store = build_s3 ( default , options) ;
87
105
88
106
let options = client_options ( CONTENT_TYPE_README , CACHE_CONTROL_README ) ;
89
- let readme_upload_store = build_s3 ( s3, options) ;
107
+ let readme_upload_store = build_s3 ( default, options) ;
108
+
109
+ let options = ClientOptions :: default ( ) ;
110
+ let index_store = build_s3 ( index, options) ;
111
+
112
+ let options = client_options ( CONTENT_TYPE_INDEX , CACHE_CONTROL_INDEX ) ;
113
+ let index_upload_store = build_s3 ( index, options) ;
90
114
91
115
Self {
92
116
store : Box :: new ( store) ,
93
117
crate_upload_store : Box :: new ( crate_upload_store) ,
94
118
readme_upload_store : Box :: new ( readme_upload_store) ,
119
+ index_store : Box :: new ( index_store) ,
120
+ index_upload_store : Box :: new ( index_upload_store) ,
95
121
}
96
122
}
97
123
98
124
StorageConfig :: LocalFileSystem { path } => {
99
- fs:: create_dir_all ( path)
100
- . context ( "Failed to create `local_uploads` directory" )
125
+ warn ! ( ?path, "Using local file system for file storage" ) ;
126
+
127
+ let index_path = path. join ( "index" ) ;
128
+
129
+ fs:: create_dir_all ( & index_path)
130
+ . context ( "Failed to create file storage directories" )
101
131
. unwrap ( ) ;
102
132
103
- warn ! ( ?path, "Using local file system for file storage" ) ;
104
133
let local = LocalFileSystem :: new_with_prefix ( path)
105
134
. context ( "Failed to initialize local file system storage" )
106
135
. unwrap ( ) ;
107
136
137
+ let local_index = LocalFileSystem :: new_with_prefix ( index_path)
138
+ . context ( "Failed to initialize local file system storage" )
139
+ . unwrap ( ) ;
140
+
108
141
let store = ArcStore :: new ( local) ;
142
+ let index_store = ArcStore :: new ( local_index) ;
143
+
109
144
Self {
110
145
store : Box :: new ( store. clone ( ) ) ,
111
146
crate_upload_store : Box :: new ( store. clone ( ) ) ,
112
147
readme_upload_store : Box :: new ( store) ,
148
+ index_store : Box :: new ( index_store. clone ( ) ) ,
149
+ index_upload_store : Box :: new ( index_store) ,
113
150
}
114
151
}
115
152
116
153
StorageConfig :: InMemory => {
117
154
warn ! ( "Using in-memory file storage" ) ;
118
155
let store = ArcStore :: new ( InMemory :: new ( ) ) ;
156
+
119
157
Self {
120
158
store : Box :: new ( store. clone ( ) ) ,
121
159
crate_upload_store : Box :: new ( store. clone ( ) ) ,
122
- readme_upload_store : Box :: new ( store) ,
160
+ readme_upload_store : Box :: new ( store. clone ( ) ) ,
161
+ index_store : Box :: new ( PrefixStore :: new ( store. clone ( ) , "index" ) ) ,
162
+ index_upload_store : Box :: new ( PrefixStore :: new ( store, "index" ) ) ,
123
163
}
124
164
}
125
165
}
@@ -173,6 +213,16 @@ impl Storage {
173
213
self . readme_upload_store . put ( & path, bytes) . await
174
214
}
175
215
216
+ #[ instrument( skip( self ) ) ]
217
+ pub async fn sync_index ( & self , name : & str , content : Option < String > ) -> Result < ( ) > {
218
+ let path = crates_io_index:: Repository :: relative_index_file_for_url ( name) . into ( ) ;
219
+ if let Some ( content) = content {
220
+ self . index_upload_store . put ( & path, content. into ( ) ) . await
221
+ } else {
222
+ self . index_store . delete ( & path) . await
223
+ }
224
+ }
225
+
176
226
/// This should only be used for assertions in the test suite!
177
227
pub fn as_inner ( & self ) -> & dyn ObjectStore {
178
228
& self . store
@@ -358,4 +408,21 @@ mod tests {
358
408
] ;
359
409
assert_eq ! ( stored_files( & s. store) . await , expected_files) ;
360
410
}
411
+
412
+ #[ tokio:: test]
413
+ async fn sync_index ( ) {
414
+ let s = Storage :: from_config ( & StorageConfig :: InMemory ) ;
415
+
416
+ assert ! ( stored_files( & s. store) . await . is_empty( ) ) ;
417
+
418
+ let content = "foo" . to_string ( ) ;
419
+ s. sync_index ( "foo" , Some ( content) ) . await . unwrap ( ) ;
420
+
421
+ let expected_files = vec ! [ "index/3/f/foo" ] ;
422
+ assert_eq ! ( stored_files( & s. store) . await , expected_files) ;
423
+
424
+ s. sync_index ( "foo" , None ) . await . unwrap ( ) ;
425
+
426
+ assert ! ( stored_files( & s. store) . await . is_empty( ) ) ;
427
+ }
361
428
}
0 commit comments