]>
Commit | Line | Data |
---|---|---|
fe692bf9 FG |
1 | //! Operations that interact with the [registry web API][1]. |
2 | //! | |
3 | //! [1]: https://doc.rust-lang.org/nightly/cargo/reference/registry-web-api.html | |
4 | ||
5 | mod login; | |
6 | mod logout; | |
7 | mod owner; | |
8 | mod publish; | |
9 | mod search; | |
10 | mod yank; | |
11 | ||
12 | use std::collections::HashSet; | |
fe692bf9 FG |
13 | use std::str; |
14 | use std::task::Poll; | |
15 | ||
16 | use anyhow::{bail, format_err, Context as _}; | |
add651ee | 17 | use cargo_credential::{Operation, Secret}; |
fe692bf9 | 18 | use crates_io::{self, Registry}; |
781aab86 | 19 | use url::Url; |
fe692bf9 | 20 | |
fe692bf9 | 21 | use crate::core::SourceId; |
781aab86 | 22 | use crate::sources::source::Source; |
fe692bf9 | 23 | use crate::sources::{RegistrySource, SourceConfigMap}; |
add651ee FG |
24 | use crate::util::auth; |
25 | use crate::util::config::{Config, PathAndArgs}; | |
fe692bf9 FG |
26 | use crate::util::errors::CargoResult; |
27 | use crate::util::network::http::http_handle; | |
fe692bf9 FG |
28 | |
29 | pub use self::login::registry_login; | |
30 | pub use self::logout::registry_logout; | |
31 | pub use self::owner::modify_owners; | |
32 | pub use self::owner::OwnersOptions; | |
33 | pub use self::publish::publish; | |
34 | pub use self::publish::PublishOpts; | |
35 | pub use self::search::search; | |
36 | pub use self::yank::yank; | |
37 | ||
781aab86 FG |
38 | /// Represents either `--registry` or `--index` argument, which is mutually exclusive. |
39 | #[derive(Debug, Clone)] | |
40 | pub enum RegistryOrIndex { | |
41 | Registry(String), | |
42 | Index(Url), | |
43 | } | |
44 | ||
45 | impl RegistryOrIndex { | |
46 | fn is_index(&self) -> bool { | |
47 | matches!(self, RegistryOrIndex::Index(..)) | |
48 | } | |
49 | } | |
50 | ||
fe692bf9 FG |
51 | /// Registry settings loaded from config files. |
52 | /// | |
53 | /// This is loaded based on the `--registry` flag and the config settings. | |
54 | #[derive(Debug, PartialEq)] | |
55 | pub enum RegistryCredentialConfig { | |
56 | None, | |
57 | /// The authentication token. | |
58 | Token(Secret<String>), | |
59 | /// Process used for fetching a token. | |
add651ee | 60 | Process(Vec<PathAndArgs>), |
fe692bf9 FG |
61 | /// Secret Key and subject for Asymmetric tokens. |
62 | AsymmetricKey((Secret<String>, Option<String>)), | |
63 | } | |
64 | ||
65 | impl RegistryCredentialConfig { | |
66 | /// Returns `true` if the credential is [`None`]. | |
67 | /// | |
68 | /// [`None`]: Self::None | |
69 | pub fn is_none(&self) -> bool { | |
70 | matches!(self, Self::None) | |
71 | } | |
72 | /// Returns `true` if the credential is [`Token`]. | |
73 | /// | |
74 | /// [`Token`]: Self::Token | |
75 | pub fn is_token(&self) -> bool { | |
76 | matches!(self, Self::Token(..)) | |
77 | } | |
78 | /// Returns `true` if the credential is [`AsymmetricKey`]. | |
79 | /// | |
80 | /// [`AsymmetricKey`]: RegistryCredentialConfig::AsymmetricKey | |
81 | pub fn is_asymmetric_key(&self) -> bool { | |
82 | matches!(self, Self::AsymmetricKey(..)) | |
83 | } | |
84 | pub fn as_token(&self) -> Option<Secret<&str>> { | |
85 | if let Self::Token(v) = self { | |
86 | Some(v.as_deref()) | |
87 | } else { | |
88 | None | |
89 | } | |
90 | } | |
add651ee | 91 | pub fn as_process(&self) -> Option<&Vec<PathAndArgs>> { |
fe692bf9 FG |
92 | if let Self::Process(v) = self { |
93 | Some(v) | |
94 | } else { | |
95 | None | |
96 | } | |
97 | } | |
98 | pub fn as_asymmetric_key(&self) -> Option<&(Secret<String>, Option<String>)> { | |
99 | if let Self::AsymmetricKey(v) = self { | |
100 | Some(v) | |
101 | } else { | |
102 | None | |
103 | } | |
104 | } | |
105 | } | |
106 | ||
107 | /// Returns the `Registry` and `Source` based on command-line and config settings. | |
108 | /// | |
109 | /// * `token_from_cmdline`: The token from the command-line. If not set, uses the token | |
110 | /// from the config. | |
111 | /// * `index`: The index URL from the command-line. | |
112 | /// * `registry`: The registry name from the command-line. If neither | |
113 | /// `registry`, or `index` are set, then uses `crates-io`. | |
114 | /// * `force_update`: If `true`, forces the index to be updated. | |
115 | /// * `token_required`: If `true`, the token will be set. | |
116 | fn registry( | |
117 | config: &Config, | |
118 | token_from_cmdline: Option<Secret<&str>>, | |
781aab86 | 119 | reg_or_index: Option<&RegistryOrIndex>, |
fe692bf9 | 120 | force_update: bool, |
add651ee | 121 | token_required: Option<Operation<'_>>, |
fe692bf9 | 122 | ) -> CargoResult<(Registry, RegistrySourceIds)> { |
781aab86 | 123 | let source_ids = get_source_id(config, reg_or_index)?; |
fe692bf9 | 124 | |
781aab86 FG |
125 | let is_index = reg_or_index.map(|v| v.is_index()).unwrap_or_default(); |
126 | if is_index && token_required.is_some() && token_from_cmdline.is_none() { | |
fe692bf9 FG |
127 | bail!("command-line argument --index requires --token to be specified"); |
128 | } | |
129 | if let Some(token) = token_from_cmdline { | |
add651ee | 130 | auth::cache_token_from_commandline(config, &source_ids.original, token); |
fe692bf9 FG |
131 | } |
132 | ||
133 | let cfg = { | |
134 | let _lock = config.acquire_package_cache_lock()?; | |
135 | let mut src = RegistrySource::remote(source_ids.replacement, &HashSet::new(), config)?; | |
136 | // Only update the index if `force_update` is set. | |
137 | if force_update { | |
138 | src.invalidate_cache() | |
139 | } | |
140 | let cfg = loop { | |
141 | match src.config()? { | |
142 | Poll::Pending => src | |
143 | .block_until_ready() | |
144 | .with_context(|| format!("failed to update {}", source_ids.replacement))?, | |
145 | Poll::Ready(cfg) => break cfg, | |
146 | } | |
147 | }; | |
148 | cfg.expect("remote registries must have config") | |
149 | }; | |
150 | let api_host = cfg | |
151 | .api | |
152 | .ok_or_else(|| format_err!("{} does not support API commands", source_ids.replacement))?; | |
153 | let token = if token_required.is_some() || cfg.auth_required { | |
add651ee | 154 | let operation = token_required.unwrap_or(Operation::Read); |
fe692bf9 FG |
155 | Some(auth::auth_token( |
156 | config, | |
157 | &source_ids.original, | |
158 | None, | |
add651ee FG |
159 | operation, |
160 | vec![], | |
781aab86 | 161 | false, |
fe692bf9 FG |
162 | )?) |
163 | } else { | |
164 | None | |
165 | }; | |
166 | let handle = http_handle(config)?; | |
167 | Ok(( | |
168 | Registry::new_handle(api_host, token, handle, cfg.auth_required), | |
169 | source_ids, | |
170 | )) | |
171 | } | |
172 | ||
173 | /// Gets the SourceId for an index or registry setting. | |
174 | /// | |
175 | /// The `index` and `reg` values are from the command-line or config settings. | |
176 | /// If both are None, and no source-replacement is configured, returns the source for crates.io. | |
177 | /// If both are None, and source replacement is configured, returns an error. | |
178 | /// | |
179 | /// The source for crates.io may be GitHub, index.crates.io, or a test-only registry depending | |
180 | /// on configuration. | |
181 | /// | |
182 | /// If `reg` is set, source replacement is not followed. | |
183 | /// | |
184 | /// The return value is a pair of `SourceId`s: The first may be a built-in replacement of | |
185 | /// crates.io (such as index.crates.io), while the second is always the original source. | |
186 | fn get_source_id( | |
187 | config: &Config, | |
781aab86 | 188 | reg_or_index: Option<&RegistryOrIndex>, |
fe692bf9 | 189 | ) -> CargoResult<RegistrySourceIds> { |
781aab86 FG |
190 | let sid = match reg_or_index { |
191 | None => SourceId::crates_io(config)?, | |
192 | Some(RegistryOrIndex::Index(url)) => SourceId::for_registry(url)?, | |
193 | Some(RegistryOrIndex::Registry(r)) => SourceId::alt_registry(config, r)?, | |
fe692bf9 FG |
194 | }; |
195 | // Load source replacements that are built-in to Cargo. | |
196 | let builtin_replacement_sid = SourceConfigMap::empty(config)? | |
197 | .load(sid, &HashSet::new())? | |
198 | .replaced_source_id(); | |
199 | let replacement_sid = SourceConfigMap::new(config)? | |
200 | .load(sid, &HashSet::new())? | |
201 | .replaced_source_id(); | |
781aab86 | 202 | if reg_or_index.is_none() && replacement_sid != builtin_replacement_sid { |
fe692bf9 FG |
203 | // Neither --registry nor --index was passed and the user has configured source-replacement. |
204 | if let Some(replacement_name) = replacement_sid.alt_registry_key() { | |
205 | bail!("crates-io is replaced with remote registry {replacement_name};\ninclude `--registry {replacement_name}` or `--registry crates-io`"); | |
206 | } else { | |
207 | bail!("crates-io is replaced with non-remote-registry source {replacement_sid};\ninclude `--registry crates-io` to use crates.io"); | |
208 | } | |
209 | } else { | |
210 | Ok(RegistrySourceIds { | |
211 | original: sid, | |
212 | replacement: builtin_replacement_sid, | |
213 | }) | |
214 | } | |
215 | } | |
216 | ||
217 | struct RegistrySourceIds { | |
218 | /// Use when looking up the auth token, or writing out `Cargo.lock` | |
219 | original: SourceId, | |
220 | /// Use when interacting with the source (querying / publishing , etc) | |
221 | /// | |
222 | /// The source for crates.io may be replaced by a built-in source for accessing crates.io with | |
223 | /// the sparse protocol, or a source for the testing framework (when the replace_crates_io | |
224 | /// function is used) | |
225 | /// | |
226 | /// User-defined source replacement is not applied. | |
227 | replacement: SourceId, | |
228 | } |