Skip to content

Commit a9d78e5

Browse files
committed
Add storage, kv, and databases commands
- storage: list/get/create/create-s3/delete - kv: list/get/create/delete - databases: list/get/create/create-postgres/delete
1 parent 98bd510 commit a9d78e5

10 files changed

Lines changed: 1318 additions & 8 deletions

File tree

README.md

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,74 @@ ow env unset production API_URL
105105
ow env delete production
106106
```
107107

108-
### Database Operations
108+
### Storage
109109

110-
Requires a `db` type alias.
110+
Manage S3/R2 storage configurations.
111+
112+
```bash
113+
# List storage configs
114+
ow storage list
115+
116+
# Get storage details
117+
ow storage get my-storage
118+
119+
# Create platform storage (shared R2)
120+
ow storage create my-storage
121+
122+
# Create S3 storage
123+
ow storage create my-s3 --provider s3 \
124+
--bucket my-bucket \
125+
--access-key-id AKIAXXXXXXXX \
126+
--secret-access-key xxxxx \
127+
--endpoint https://s3.amazonaws.com
128+
129+
# Delete storage
130+
ow storage delete my-storage
131+
```
132+
133+
### KV
134+
135+
Manage key-value namespaces.
136+
137+
```bash
138+
# List KV namespaces
139+
ow kv list
140+
141+
# Get KV details
142+
ow kv get my-kv
143+
144+
# Create KV namespace
145+
ow kv create my-kv -d "Cache storage"
146+
147+
# Delete KV namespace
148+
ow kv delete my-kv
149+
```
150+
151+
### Databases
152+
153+
Manage database bindings.
154+
155+
```bash
156+
# List databases
157+
ow databases list
158+
159+
# Get database details
160+
ow databases get my-db
161+
162+
# Create platform database (shared)
163+
ow databases create my-db
164+
165+
# Create Postgres binding
166+
ow databases create my-pg --provider postgres \
167+
--connection-string "postgres://user:pass@host/db"
168+
169+
# Delete database
170+
ow databases delete my-db
171+
```
172+
173+
### Infra
174+
175+
Database migrations (requires `db` type alias).
111176

112177
```bash
113178
# Run pending migrations

src/backend/api.rs

Lines changed: 276 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::{
2-
Backend, BackendError, CreateEnvironmentInput, CreateWorkerInput, DeployInput, Deployment,
3-
Environment, UpdateEnvironmentInput, Worker,
2+
Backend, BackendError, CreateDatabaseInput, CreateEnvironmentInput, CreateKvInput,
3+
CreateStorageInput, CreateWorkerInput, Database, DeployInput, Deployment, Environment,
4+
KvNamespace, StorageConfig, UpdateEnvironmentInput, Worker,
45
};
56
use reqwest::Client;
67

@@ -281,4 +282,277 @@ impl Backend for ApiBackend {
281282

282283
Ok(())
283284
}
285+
286+
// Storage methods
287+
async fn list_storage(&self) -> Result<Vec<StorageConfig>, BackendError> {
288+
let response = self
289+
.request(reqwest::Method::GET, "/storage")
290+
.send()
291+
.await?;
292+
293+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
294+
return Err(BackendError::Unauthorized);
295+
}
296+
297+
if !response.status().is_success() {
298+
let text = response.text().await.unwrap_or_default();
299+
return Err(BackendError::Api(text));
300+
}
301+
302+
let configs: Vec<StorageConfig> = response.json().await?;
303+
Ok(configs)
304+
}
305+
306+
async fn get_storage(&self, name: &str) -> Result<StorageConfig, BackendError> {
307+
let response = self
308+
.request(reqwest::Method::GET, &format!("/storage/{}", name))
309+
.send()
310+
.await?;
311+
312+
if response.status() == reqwest::StatusCode::NOT_FOUND {
313+
return Err(BackendError::NotFound(format!(
314+
"Storage '{}' not found",
315+
name
316+
)));
317+
}
318+
319+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
320+
return Err(BackendError::Unauthorized);
321+
}
322+
323+
if !response.status().is_success() {
324+
let text = response.text().await.unwrap_or_default();
325+
return Err(BackendError::Api(text));
326+
}
327+
328+
let config: StorageConfig = response.json().await?;
329+
Ok(config)
330+
}
331+
332+
async fn create_storage(
333+
&self,
334+
input: CreateStorageInput,
335+
) -> Result<StorageConfig, BackendError> {
336+
let response = self
337+
.request(reqwest::Method::POST, "/storage")
338+
.json(&input)
339+
.send()
340+
.await?;
341+
342+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
343+
return Err(BackendError::Unauthorized);
344+
}
345+
346+
if !response.status().is_success() {
347+
let text = response.text().await.unwrap_or_default();
348+
return Err(BackendError::Api(text));
349+
}
350+
351+
let config: StorageConfig = response.json().await?;
352+
Ok(config)
353+
}
354+
355+
async fn delete_storage(&self, name: &str) -> Result<(), BackendError> {
356+
let response = self
357+
.request(reqwest::Method::DELETE, &format!("/storage/{}", name))
358+
.send()
359+
.await?;
360+
361+
if response.status() == reqwest::StatusCode::NOT_FOUND {
362+
return Err(BackendError::NotFound(format!(
363+
"Storage '{}' not found",
364+
name
365+
)));
366+
}
367+
368+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
369+
return Err(BackendError::Unauthorized);
370+
}
371+
372+
if !response.status().is_success() {
373+
let text = response.text().await.unwrap_or_default();
374+
return Err(BackendError::Api(text));
375+
}
376+
377+
Ok(())
378+
}
379+
380+
// KV methods
381+
async fn list_kv(&self) -> Result<Vec<KvNamespace>, BackendError> {
382+
let response = self.request(reqwest::Method::GET, "/kv").send().await?;
383+
384+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
385+
return Err(BackendError::Unauthorized);
386+
}
387+
388+
if !response.status().is_success() {
389+
let text = response.text().await.unwrap_or_default();
390+
return Err(BackendError::Api(text));
391+
}
392+
393+
let namespaces: Vec<KvNamespace> = response.json().await?;
394+
Ok(namespaces)
395+
}
396+
397+
async fn get_kv(&self, name: &str) -> Result<KvNamespace, BackendError> {
398+
let response = self
399+
.request(reqwest::Method::GET, &format!("/kv/{}", name))
400+
.send()
401+
.await?;
402+
403+
if response.status() == reqwest::StatusCode::NOT_FOUND {
404+
return Err(BackendError::NotFound(format!(
405+
"KV namespace '{}' not found",
406+
name
407+
)));
408+
}
409+
410+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
411+
return Err(BackendError::Unauthorized);
412+
}
413+
414+
if !response.status().is_success() {
415+
let text = response.text().await.unwrap_or_default();
416+
return Err(BackendError::Api(text));
417+
}
418+
419+
let namespace: KvNamespace = response.json().await?;
420+
Ok(namespace)
421+
}
422+
423+
async fn create_kv(&self, input: CreateKvInput) -> Result<KvNamespace, BackendError> {
424+
let response = self
425+
.request(reqwest::Method::POST, "/kv")
426+
.json(&input)
427+
.send()
428+
.await?;
429+
430+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
431+
return Err(BackendError::Unauthorized);
432+
}
433+
434+
if !response.status().is_success() {
435+
let text = response.text().await.unwrap_or_default();
436+
return Err(BackendError::Api(text));
437+
}
438+
439+
let namespace: KvNamespace = response.json().await?;
440+
Ok(namespace)
441+
}
442+
443+
async fn delete_kv(&self, name: &str) -> Result<(), BackendError> {
444+
let response = self
445+
.request(reqwest::Method::DELETE, &format!("/kv/{}", name))
446+
.send()
447+
.await?;
448+
449+
if response.status() == reqwest::StatusCode::NOT_FOUND {
450+
return Err(BackendError::NotFound(format!(
451+
"KV namespace '{}' not found",
452+
name
453+
)));
454+
}
455+
456+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
457+
return Err(BackendError::Unauthorized);
458+
}
459+
460+
if !response.status().is_success() {
461+
let text = response.text().await.unwrap_or_default();
462+
return Err(BackendError::Api(text));
463+
}
464+
465+
Ok(())
466+
}
467+
468+
// Database methods
469+
async fn list_databases(&self) -> Result<Vec<Database>, BackendError> {
470+
let response = self
471+
.request(reqwest::Method::GET, "/databases")
472+
.send()
473+
.await?;
474+
475+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
476+
return Err(BackendError::Unauthorized);
477+
}
478+
479+
if !response.status().is_success() {
480+
let text = response.text().await.unwrap_or_default();
481+
return Err(BackendError::Api(text));
482+
}
483+
484+
let databases: Vec<Database> = response.json().await?;
485+
Ok(databases)
486+
}
487+
488+
async fn get_database(&self, name: &str) -> Result<Database, BackendError> {
489+
let response = self
490+
.request(reqwest::Method::GET, &format!("/databases/{}", name))
491+
.send()
492+
.await?;
493+
494+
if response.status() == reqwest::StatusCode::NOT_FOUND {
495+
return Err(BackendError::NotFound(format!(
496+
"Database '{}' not found",
497+
name
498+
)));
499+
}
500+
501+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
502+
return Err(BackendError::Unauthorized);
503+
}
504+
505+
if !response.status().is_success() {
506+
let text = response.text().await.unwrap_or_default();
507+
return Err(BackendError::Api(text));
508+
}
509+
510+
let database: Database = response.json().await?;
511+
Ok(database)
512+
}
513+
514+
async fn create_database(&self, input: CreateDatabaseInput) -> Result<Database, BackendError> {
515+
let response = self
516+
.request(reqwest::Method::POST, "/databases")
517+
.json(&input)
518+
.send()
519+
.await?;
520+
521+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
522+
return Err(BackendError::Unauthorized);
523+
}
524+
525+
if !response.status().is_success() {
526+
let text = response.text().await.unwrap_or_default();
527+
return Err(BackendError::Api(text));
528+
}
529+
530+
let database: Database = response.json().await?;
531+
Ok(database)
532+
}
533+
534+
async fn delete_database(&self, name: &str) -> Result<(), BackendError> {
535+
let response = self
536+
.request(reqwest::Method::DELETE, &format!("/databases/{}", name))
537+
.send()
538+
.await?;
539+
540+
if response.status() == reqwest::StatusCode::NOT_FOUND {
541+
return Err(BackendError::NotFound(format!(
542+
"Database '{}' not found",
543+
name
544+
)));
545+
}
546+
547+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
548+
return Err(BackendError::Unauthorized);
549+
}
550+
551+
if !response.status().is_success() {
552+
let text = response.text().await.unwrap_or_default();
553+
return Err(BackendError::Api(text));
554+
}
555+
556+
Ok(())
557+
}
284558
}

0 commit comments

Comments
 (0)