shadow_drive_cli/command/drive/
mod.rs

1use crate::utils::{
2    get_text, last_modified, parse_filesize, process_shadow_api_response, pubkey_arg,
3    shadow_client_factory, shadow_file_with_basename, storage_object_url,
4    wait_for_user_confirmation, FileMetadata, FILE_UPLOAD_BATCH_SIZE,
5};
6use byte_unit::Byte;
7use clap::Parser;
8use futures::StreamExt;
9use shadow_drive_sdk::{Pubkey, ShadowDriveClient, StorageAccountVersion};
10use shadow_rpc_auth::genesysgo_auth::{authenticate, parse_account_id_from_url};
11use solana_sdk::signature::Signer;
12use std::path::PathBuf;
13
14#[derive(Debug, Parser)]
15pub enum DriveCommand {
16    ShadowRpcAuth,
17    /// Create an account on which to store data.
18    /// Storage accounts can be globally, irreversibly marked immutable
19    /// for a one-time fee.
20    /// Otherwise, files can be added or deleted from them, and space
21    /// rented indefinitely.
22    CreateStorageAccount {
23        /// Unique identifier for your storage account
24        name: String,
25        /// File size string, accepts KB, MB, GB, e.g. "10MB"
26        #[clap(parse(try_from_str = parse_filesize))]
27        size: Byte,
28    },
29    /// Queues a storage account for deletion. While the request is
30    /// still enqueued and not yet carried out, a cancellation
31    /// can be made (see cancel-delete-storage-account subcommand).
32    DeleteStorageAccount {
33        /// The account to delete
34        #[clap(parse(try_from_str = pubkey_arg))]
35        storage_account: Pubkey,
36    },
37    /// Cancels the deletion of a storage account enqueued for deletion.
38    CancelDeleteStorageAccount {
39        /// The account for which to cancel deletion.
40        #[clap(parse(try_from_str = pubkey_arg))]
41        storage_account: Pubkey,
42    },
43    /// Redeem tokens afforded to a storage account after reducing storage capacity.
44    ClaimStake {
45        /// The account whose stake to claim.
46        #[clap(parse(try_from_str = pubkey_arg))]
47        storage_account: Pubkey,
48    },
49    /// Increase the capacity of a storage account.
50    AddStorage {
51        /// Storage account to modify
52        #[clap(parse(try_from_str = pubkey_arg))]
53        storage_account: Pubkey,
54        /// File size string, accepts KB, MB, GB, e.g. "10MB"
55        #[clap(parse(try_from_str = parse_filesize))]
56        size: Byte,
57    },
58    /// Increase the immutable storage capacity of a storage account.
59    AddImmutableStorage {
60        /// Storage account to modify
61        #[clap(parse(try_from_str = pubkey_arg))]
62        storage_account: Pubkey,
63        /// File size string, accepts KB, MB, GB, e.g. "10MB"
64        #[clap(parse(try_from_str = parse_filesize))]
65        size: Byte,
66    },
67    /// Reduce the capacity of a storage account.
68    ReduceStorage {
69        /// Storage account to modify
70        #[clap(parse(try_from_str = pubkey_arg))]
71        storage_account: Pubkey,
72        /// File size string, accepts KB, MB, GB, e.g. "10MB"
73        #[clap(parse(try_from_str = parse_filesize))]
74        size: Byte,
75    },
76    /// Make a storage account immutable. This is irreversible.
77    MakeStorageImmutable {
78        /// Storage account to be marked immutable
79        #[clap(parse(try_from_str = pubkey_arg))]
80        storage_account: Pubkey,
81    },
82    /// Fetch the metadata pertaining to a storage account.
83    GetStorageAccount {
84        /// Account whose metadata will be fetched.
85        #[clap(parse(try_from_str = pubkey_arg))]
86        storage_account: Pubkey,
87    },
88    /// Fetch a list of storage accounts owned by a particular pubkey.
89    /// If no owner is provided, the configured signer is used.
90    GetStorageAccounts {
91        /// Searches for storage accounts owned by this owner.
92        #[clap(parse(try_from_str = pubkey_arg))]
93        owner: Option<Pubkey>,
94    },
95    /// List all the files in a storage account.
96    ListFiles {
97        /// Storage account whose files to list.
98        #[clap(parse(try_from_str = pubkey_arg))]
99        storage_account: Pubkey,
100    },
101    /// Get a file, assume it's text, and print it.
102    GetText {
103        /// Storage account where the file is located.
104        #[clap(parse(try_from_str = pubkey_arg))]
105        storage_account: Pubkey,
106        /// Name of the file to fetch
107        filename: String,
108    },
109    /// Get basic file object data from a storage account file.
110    GetObjectData {
111        /// Storage account where the file is located.
112        #[clap(parse(try_from_str = pubkey_arg))]
113        storage_account: Pubkey,
114        /// Name of the file to examine.
115        file: String,
116    },
117    /// Delete a file from a storage account.
118    DeleteFile {
119        /// Storage account where the file to delete is located.
120        #[clap(parse(try_from_str = pubkey_arg))]
121        storage_account: Pubkey,
122        /// Name of the file to delete.
123        filename: String,
124    },
125    /// Has to be the same name as a previously uploaded file
126    EditFile {
127        /// Storage account where the file to edit is located.
128        #[clap(parse(try_from_str = pubkey_arg))]
129        storage_account: Pubkey,
130        /// Path to the new version of the file. Must be the same
131        /// name as the file you are editing.
132        path: PathBuf,
133    },
134    /// Upload one or more files to a storage account.
135    StoreFiles {
136        /// Batch size for file uploads, default 100, only relevant for large
137        /// numbers of uploads
138        #[clap(long, default_value_t=FILE_UPLOAD_BATCH_SIZE)]
139        batch_size: usize,
140        /// The storage account on which to upload the files
141        #[clap(parse(try_from_str = pubkey_arg))]
142        storage_account: Pubkey,
143        /// A list of one or more filepaths, each of which is to be uploaded.
144        #[clap(min_values = 1)]
145        files: Vec<PathBuf>,
146    },
147}
148
149impl DriveCommand {
150    pub async fn process<T: Signer>(
151        &self,
152        signer: &T,
153        client_signer: T,
154        rpc_url: &str,
155        skip_confirm: bool,
156        auth: Option<String>,
157    ) -> anyhow::Result<()> {
158        let signer_pubkey = signer.pubkey();
159        println!("Signing with {:?}", signer_pubkey);
160        println!("Sending RPC requests to {}", rpc_url);
161        match self {
162            DriveCommand::ShadowRpcAuth => {
163                let account_id = parse_account_id_from_url(rpc_url.to_string())?;
164                let resp = authenticate(signer as &dyn Signer, &account_id).await?;
165                println!("{:#?}", resp);
166            }
167            DriveCommand::CreateStorageAccount { name, size } => {
168                let client = shadow_client_factory(client_signer, rpc_url, auth);
169                println!("Create Storage Account {}: {}", name, size);
170                wait_for_user_confirmation(skip_confirm)?;
171                let response = client
172                    .create_storage_account(name, size.clone(), StorageAccountVersion::v2())
173                    .await;
174                let resp = process_shadow_api_response(response)?;
175                println!("{:#?}", resp);
176            }
177            DriveCommand::DeleteStorageAccount { storage_account } => {
178                let client = shadow_client_factory(client_signer, rpc_url, auth);
179                println!("Delete Storage Account {}", storage_account.to_string());
180                wait_for_user_confirmation(skip_confirm)?;
181                let response = client.delete_storage_account(storage_account).await;
182
183                let resp = process_shadow_api_response(response)?;
184                println!("{:#?}", resp);
185            }
186            DriveCommand::CancelDeleteStorageAccount { storage_account } => {
187                let client = shadow_client_factory(client_signer, rpc_url, auth);
188                println!(
189                    "Cancellation of Delete Storage Account {}",
190                    storage_account.to_string()
191                );
192                wait_for_user_confirmation(skip_confirm)?;
193                let response = client.cancel_delete_storage_account(storage_account).await;
194
195                let resp = process_shadow_api_response(response)?;
196                println!("{:#?}", resp);
197            }
198            DriveCommand::ClaimStake { storage_account } => {
199                let client = shadow_client_factory(client_signer, rpc_url, auth);
200                println!(
201                    "Claim Stake on Storage Account {}",
202                    storage_account.to_string()
203                );
204                wait_for_user_confirmation(skip_confirm)?;
205                let response = client.claim_stake(storage_account).await;
206
207                let resp = process_shadow_api_response(response)?;
208                println!("{:#?}", resp);
209            }
210            DriveCommand::ReduceStorage {
211                storage_account,
212                size,
213            } => {
214                let client = shadow_client_factory(client_signer, rpc_url, auth);
215                println!(
216                    "Reduce Storage Capacity {}: {}",
217                    storage_account.to_string(),
218                    size
219                );
220                wait_for_user_confirmation(skip_confirm)?;
221                let response = client.reduce_storage(storage_account, size.clone()).await;
222
223                let resp = process_shadow_api_response(response)?;
224                println!("{:#?}", resp);
225            }
226            DriveCommand::AddStorage {
227                storage_account,
228                size,
229            } => {
230                let client = shadow_client_factory(client_signer, rpc_url, auth);
231                println!("Increase Storage {}: {}", storage_account.to_string(), size);
232                wait_for_user_confirmation(skip_confirm)?;
233                let response = client.add_storage(storage_account, size.clone()).await;
234
235                let resp = process_shadow_api_response(response)?;
236                println!("{:#?}", resp);
237            }
238            DriveCommand::AddImmutableStorage {
239                storage_account,
240                size,
241            } => {
242                let client = shadow_client_factory(client_signer, rpc_url, auth);
243                println!(
244                    "Increase Immutable Storage {}: {}",
245                    storage_account.to_string(),
246                    size
247                );
248                wait_for_user_confirmation(skip_confirm)?;
249                let response = client
250                    .add_immutable_storage(storage_account, size.clone())
251                    .await;
252
253                let resp = process_shadow_api_response(response)?;
254                println!("{:#?}", resp);
255            }
256            DriveCommand::MakeStorageImmutable { storage_account } => {
257                let client = shadow_client_factory(client_signer, rpc_url, auth);
258                println!("Make Storage Immutable {}", storage_account.to_string());
259                wait_for_user_confirmation(skip_confirm)?;
260                let response = client.make_storage_immutable(storage_account).await;
261
262                let resp = process_shadow_api_response(response)?;
263                println!("{:#?}", resp);
264            }
265            DriveCommand::GetStorageAccount { storage_account } => {
266                let client = ShadowDriveClient::new(client_signer, rpc_url);
267                println!("Get Storage Account {}", storage_account.to_string());
268                let response = client.get_storage_account(storage_account).await;
269
270                let act = process_shadow_api_response(response)?;
271                println!("{:#?}", act);
272            }
273            DriveCommand::GetStorageAccounts { owner } => {
274                let client = shadow_client_factory(client_signer, rpc_url, auth.clone());
275                let owner = owner.as_ref().unwrap_or(&signer_pubkey);
276                println!("Get Storage Accounts Owned By {}", owner.to_string());
277                let response = client.get_storage_accounts(owner).await;
278                let accounts = process_shadow_api_response(response)?;
279                println!("{:#?}", accounts);
280            }
281            DriveCommand::ListFiles { storage_account } => {
282                let client = ShadowDriveClient::new(client_signer, rpc_url);
283                println!(
284                    "List Files for Storage Account {}",
285                    storage_account.to_string()
286                );
287                let response = client.list_objects(storage_account).await;
288                let files = process_shadow_api_response(response)?;
289                println!("{:#?}", files);
290            }
291            DriveCommand::GetText {
292                storage_account,
293                filename,
294            } => {
295                let url = storage_object_url(storage_account, filename);
296                let resp = get_text(&url).await?;
297                let last_modified = last_modified(resp.headers())?;
298                println!("Get Text at {}", &url);
299                println!("Last Modified: {}", last_modified);
300                println!("");
301                println!("{}", resp.text().await?);
302            }
303            DriveCommand::DeleteFile {
304                storage_account,
305                filename,
306            } => {
307                let client = ShadowDriveClient::new(client_signer, rpc_url);
308                let url = storage_object_url(storage_account, filename);
309                println!("Delete file {}", &url);
310                wait_for_user_confirmation(skip_confirm)?;
311                let response = client.delete_file(storage_account, url.clone()).await;
312                let resp = process_shadow_api_response(response)?;
313                println!("{:#?}", resp);
314            }
315            DriveCommand::EditFile {
316                storage_account,
317                path,
318            } => {
319                let client = ShadowDriveClient::new(client_signer, rpc_url);
320                let shadow_file = shadow_file_with_basename(path);
321                println!(
322                    "Edit file {} {}",
323                    storage_account.to_string(),
324                    path.display()
325                );
326                wait_for_user_confirmation(skip_confirm)?;
327                let response = client.edit_file(storage_account, shadow_file).await;
328                let resp = process_shadow_api_response(response)?;
329                println!("{:#?}", resp);
330            }
331            DriveCommand::GetObjectData {
332                storage_account,
333                file,
334            } => {
335                let url = storage_object_url(storage_account, file);
336                println!("Get object data {} {}", storage_account.to_string(), file);
337                let http_client = reqwest::Client::new();
338                let response = http_client.head(url).send().await?;
339                let data = FileMetadata::from_headers(response.headers())?;
340                println!("{:#?}", data);
341            }
342            DriveCommand::StoreFiles {
343                batch_size,
344                storage_account,
345                files,
346            } => {
347                let client = ShadowDriveClient::new(client_signer, rpc_url);
348                println!("Store Files {} {:#?}", storage_account.to_string(), files);
349                println!(
350                    "WARNING: This CLI does not add any encryption on its own. \
351                The files in their current state become public as soon as they're uploaded."
352                );
353                wait_for_user_confirmation(skip_confirm)?;
354                let mut responses = Vec::new();
355                for chunk in files.chunks(*batch_size) {
356                    let response = async {
357                        let resp = client
358                            .store_files(
359                                &storage_account,
360                                chunk
361                                    .into_iter()
362                                    .map(|path: &PathBuf| shadow_file_with_basename(path))
363                                    .collect(),
364                            )
365                            .await;
366                        let resp = process_shadow_api_response(resp).unwrap();
367                        println!("{:#?}", resp);
368                    };
369                    responses.push(response);
370                }
371                futures::stream::iter(responses)
372                    .buffer_unordered(100)
373                    .collect::<Vec<_>>()
374                    .await;
375            }
376        }
377        Ok(())
378    }
379}