2
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
5
- //! Serve the Repo Depot API from one or more extracted TUF repos
5
+ //! Serve the Repo Depot API from one or more Omicron TUF repos
6
6
7
7
use anyhow:: Context ;
8
8
use anyhow:: anyhow;
@@ -16,18 +16,24 @@ use dropshot::HttpResponseOk;
16
16
use dropshot:: Path ;
17
17
use dropshot:: RequestContext ;
18
18
use dropshot:: ServerBuilder ;
19
+ use futures:: StreamExt ;
19
20
use futures:: stream:: TryStreamExt ;
21
+ use libc:: SIGINT ;
20
22
use repo_depot_api:: ArtifactPathParams ;
21
23
use repo_depot_api:: RepoDepotApi ;
24
+ use signal_hook_tokio:: Signals ;
22
25
use slog:: info;
23
26
use slog_error_chain:: InlineErrorChain ;
24
27
use std:: collections:: BTreeMap ;
25
28
use std:: net:: SocketAddr ;
26
29
use std:: sync:: Arc ;
27
- use tough :: Repository ;
28
- use tough :: TargetName ;
30
+ use tokio :: io :: AsyncRead ;
31
+ use tokio_util :: io :: ReaderStream ;
29
32
use tufaceous_artifact:: ArtifactHash ;
30
- use tufaceous_lib:: OmicronRepo ;
33
+ use tufaceous_artifact:: ArtifactHashId ;
34
+ use update_common:: artifacts:: {
35
+ ArtifactsWithPlan , ControlPlaneZonesMode , VerificationMode ,
36
+ } ;
31
37
32
38
fn main ( ) -> Result < ( ) , anyhow:: Error > {
33
39
oxide_tokio_rt:: run ( async {
@@ -42,7 +48,7 @@ fn main() -> Result<(), anyhow::Error> {
42
48
} )
43
49
}
44
50
45
- /// Serve the Repo Depot API from one or more extracted TUF repos
51
+ /// Serve the Repo Depot API from one or more Omicron TUF repos
46
52
#[ derive( Debug , Parser ) ]
47
53
struct RepoDepotStandalone {
48
54
/// log level filter
@@ -58,9 +64,9 @@ struct RepoDepotStandalone {
58
64
#[ arg( long, default_value = "[::]:0" ) ]
59
65
listen_addr : SocketAddr ,
60
66
61
- /// paths to local extracted Omicron TUF repositories
67
+ /// paths to Omicron TUF repositories (zip files)
62
68
#[ arg( required = true , num_args = 1 ..) ]
63
- repo_paths : Vec < Utf8PathBuf > ,
69
+ zip_files : Vec < Utf8PathBuf > ,
64
70
}
65
71
66
72
fn parse_dropshot_log_level (
@@ -77,15 +83,33 @@ impl RepoDepotStandalone {
77
83
. to_logger ( "repo-depot-standalone" )
78
84
. context ( "failed to create logger" ) ?;
79
85
86
+ // Gracefully handle SIGINT so that we clean up the files that got
87
+ // extracted to a temporary directory.
88
+ let signals =
89
+ Signals :: new ( & [ SIGINT ] ) . expect ( "failed to wait for SIGINT" ) ;
90
+ let mut signal_stream = signals. fuse ( ) ;
91
+
80
92
let mut ctx = RepoMetadata :: new ( ) ;
81
- for repo_path in & self . repo_paths {
82
- let omicron_repo =
83
- OmicronRepo :: load_untrusted_ignore_expiration ( & log, repo_path)
84
- . await
85
- . with_context ( || {
86
- format ! ( "loading repository at {repo_path}" )
87
- } ) ?;
88
- ctx. load_repo ( omicron_repo)
93
+ for repo_path in & self . zip_files {
94
+ let file = std:: fs:: File :: open ( repo_path)
95
+ . with_context ( || format ! ( "open {:?}" , repo_path) ) ?;
96
+ let buf = std:: io:: BufReader :: new ( file) ;
97
+ info ! (
98
+ & log,
99
+ "extracting Omicron TUF repository" ;
100
+ "path" => %repo_path
101
+ ) ;
102
+ let plan = ArtifactsWithPlan :: from_zip (
103
+ buf,
104
+ None ,
105
+ ArtifactHash ( [ 0 ; 32 ] ) ,
106
+ ControlPlaneZonesMode :: Split ,
107
+ VerificationMode :: BlindlyTrustAnything ,
108
+ & log,
109
+ )
110
+ . await
111
+ . with_context ( || format ! ( "load {:?}" , repo_path) ) ?;
112
+ ctx. load_repo ( plan)
89
113
. context ( "loading artifacts from repository at {repo_path}" ) ?;
90
114
info ! ( & log, "loaded Omicron TUF repository" ; "path" => %repo_path) ;
91
115
}
@@ -95,59 +119,84 @@ impl RepoDepotStandalone {
95
119
> ( )
96
120
. unwrap ( ) ;
97
121
98
- let server = ServerBuilder :: new ( my_api, Arc :: new ( ctx) , log)
122
+ let server = ServerBuilder :: new ( my_api, Arc :: new ( ctx) , log. clone ( ) )
99
123
. config ( dropshot:: ConfigDropshot {
100
124
bind_address : self . listen_addr ,
101
125
..Default :: default ( )
102
126
} )
103
127
. start ( )
104
128
. context ( "failed to create server" ) ?;
105
129
106
- server. await . map_err ( |error| anyhow ! ( "server shut down: {error}" ) )
130
+ // Wait for a signal.
131
+ let caught_signal = signal_stream. next ( ) . await ;
132
+ assert_eq ! ( caught_signal. unwrap( ) , SIGINT ) ;
133
+ info ! (
134
+ & log,
135
+ "caught signal, shutting down and removing \
136
+ temporary directories"
137
+ ) ;
138
+
139
+ // The temporary files are deleted by `Drop` handlers so all we need to
140
+ // do is shut down gracefully.
141
+ server
142
+ . close ( )
143
+ . await
144
+ . map_err ( |e| anyhow ! ( "error closing HTTP server: {e}" ) )
107
145
}
108
146
}
109
147
110
148
/// Keeps metadata that allows us to fetch a target from any of the TUF repos
111
149
/// based on its hash.
112
150
struct RepoMetadata {
113
- repos : Vec < OmicronRepo > ,
114
- targets_by_hash : BTreeMap < ArtifactHash , ( usize , TargetName ) > ,
151
+ repos : Vec < ArtifactsWithPlan > ,
152
+ targets_by_hash : BTreeMap < ArtifactHash , ( usize , ArtifactHashId ) > ,
115
153
}
116
154
117
155
impl RepoMetadata {
118
156
pub fn new ( ) -> RepoMetadata {
119
157
RepoMetadata { repos : Vec :: new ( ) , targets_by_hash : BTreeMap :: new ( ) }
120
158
}
121
159
122
- pub fn load_repo (
123
- & mut self ,
124
- omicron_repo : OmicronRepo ,
125
- ) -> anyhow:: Result < ( ) > {
160
+ pub fn load_repo ( & mut self , plan : ArtifactsWithPlan ) -> anyhow:: Result < ( ) > {
126
161
let repo_index = self . repos . len ( ) ;
127
162
128
- let tuf_repo = omicron_repo. repo ( ) ;
129
- for ( target_name, target) in & tuf_repo. targets ( ) . signed . targets {
130
- let target_hash: & [ u8 ] = & target. hashes . sha256 ;
131
- let target_hash_array: [ u8 ; 32 ] = target_hash
132
- . try_into ( )
133
- . context ( "sha256 hash wasn't 32 bytes" ) ?;
134
- let artifact_hash = ArtifactHash ( target_hash_array) ;
163
+ for artifact_meta in & plan. description ( ) . artifacts {
164
+ let artifact_hash = artifact_meta. hash ;
165
+ let artifact_id = & artifact_meta. id ;
166
+ let artifact_hash_id = ArtifactHashId {
167
+ kind : artifact_id. kind . clone ( ) ,
168
+ hash : artifact_hash,
169
+ } ;
170
+
171
+ // Some hashes appear multiple times, whether in the same repo or
172
+ // different repos. That's fine. They all have the same contents
173
+ // so we can serve any of them when this hash is requested.
135
174
self . targets_by_hash
136
- . insert ( artifact_hash , ( repo_index, target_name . clone ( ) ) ) ;
175
+ . insert ( artifact_meta . hash , ( repo_index, artifact_hash_id ) ) ;
137
176
}
138
177
139
- self . repos . push ( omicron_repo ) ;
178
+ self . repos . push ( plan ) ;
140
179
Ok ( ( ) )
141
180
}
142
181
143
- pub fn repo_and_target_name_for_hash (
182
+ pub async fn data_for_hash (
144
183
& self ,
145
184
requested_sha : & ArtifactHash ,
146
- ) -> Option < ( & Repository , & TargetName ) > {
147
- let ( repo_index, target_name ) =
185
+ ) -> Option < anyhow :: Result < ReaderStream < impl AsyncRead > > > {
186
+ let ( repo_index, artifact_hash_id ) =
148
187
self . targets_by_hash . get ( requested_sha) ?;
149
- let omicron_repo = & self . repos [ * repo_index] ;
150
- Some ( ( omicron_repo. repo ( ) , target_name) )
188
+ let repo = & self . repos [ * repo_index] ;
189
+ Some (
190
+ repo. get_by_hash ( artifact_hash_id)
191
+ . unwrap_or_else ( || {
192
+ panic ! (
193
+ "artifact hash unexpectedly missing from the repo that \
194
+ we recorded having found it in"
195
+ )
196
+ } )
197
+ . reader_stream ( )
198
+ . await ,
199
+ )
151
200
}
152
201
}
153
202
@@ -162,29 +211,19 @@ impl RepoDepotApi for StandaloneApiImpl {
162
211
) -> Result < HttpResponseOk < FreeformBody > , HttpError > {
163
212
let repo_metadata = rqctx. context ( ) ;
164
213
let requested_sha = & path_params. into_inner ( ) . sha256 ;
165
- let ( tuf_repo, target_name) = repo_metadata
166
- . repo_and_target_name_for_hash ( requested_sha)
214
+ let reader = repo_metadata
215
+ . data_for_hash ( requested_sha)
216
+ . await
167
217
. ok_or_else ( || {
168
218
HttpError :: for_not_found (
169
219
None ,
170
220
String :: from ( "found no target with this hash" ) ,
171
221
)
172
- } ) ?;
173
-
174
- let reader = tuf_repo
175
- . read_target ( & target_name)
176
- . await
222
+ } ) ?
177
223
. map_err ( |error| {
178
224
HttpError :: for_internal_error ( format ! (
179
- "failed to read target from TUF repo: {}" ,
180
- InlineErrorChain :: new( & error) ,
181
- ) )
182
- } ) ?
183
- . ok_or_else ( || {
184
- // We already checked above that the hash is present in the TUF
185
- // repo so this should not be a 404.
186
- HttpError :: for_internal_error ( String :: from (
187
- "missing target from TUF repo" ,
225
+ "loading file from TUF repo: {}" ,
226
+ InlineErrorChain :: new( & * error) ,
188
227
) )
189
228
} ) ?;
190
229
let mut buf_list =
0 commit comments