Skip to content

Commit e8c559e

Browse files
committed
Add projects, databases DB backend, link_worker_environment, interactive secrets
- Add projects command (list, delete) with DB backend - Implement databases CRUD in DB backend (was API-only) - Add DatabaseProvider enum (platform/postgres) with clap ValueEnum - Add link_worker_environment procedure call (replaces update_worker hack) - Interactive secret input with rpassword (masked) for `env set --secret` - Prompt plain value when omitted from `env set` - Mask secrets in `env get` output - Extract ObjectStorage trait in s3.rs (S3Client + PresignedClient) - Unified upload_assets with 10-way concurrency and HEAD dedup - Direct S3 upload from DB backend (DirectUploadConfig) - Add migrations 26 (link_worker_environment) and 27 (normalize platform DB UUID) - Add database.sh backup/restore script - Bump to v0.3.4
1 parent 1ce5795 commit e8c559e

File tree

18 files changed

+921
-262
lines changed

18 files changed

+921
-262
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"binstall",
44
"chrono",
55
"codegen",
6+
"postgate",
67
"prerendered",
78
"psql",
89
"reqwest",

Cargo.lock

Lines changed: 32 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "openworkers-cli"
3-
version = "0.3.3"
3+
version = "0.3.4"
44
edition = "2024"
55
license = "MIT"
66
description = "CLI for OpenWorkers - Self-hosted Cloudflare Workers runtime"
@@ -63,6 +63,7 @@ futures = "0.3"
6363
url = "2"
6464
rmcp = { version = "0.15", features = ["macros", "server", "transport-io"], optional = true }
6565
schemars = { version = "1", optional = true }
66+
rpassword = "7.4.0"
6667

6768
# https://doc.rust-lang.org/cargo/reference/profiles.html
6869
# https://github.com/johnthagen/min-sized-rust?tab=readme-ov-file#minimizing-rust-binary-size
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
-- ============================================================================
2+
-- Procedure to link an environment to a worker.
3+
--
4+
-- If the worker belongs to a project, cascades the environment to the project
5+
-- and all sibling workers in a single transaction.
6+
-- Replaces the trigger-based constraint with an explicit procedure.
7+
-- ============================================================================
8+
9+
-- Drop the old triggers that blocked the operation
10+
DROP TRIGGER IF EXISTS check_workers_project_environment ON workers;
11+
DROP TRIGGER IF EXISTS check_projects_environment ON projects;
12+
DROP FUNCTION IF EXISTS check_worker_project_environment();
13+
14+
CREATE OR REPLACE FUNCTION link_worker_environment(
15+
p_worker_id uuid,
16+
p_environment_id uuid
17+
)
18+
RETURNS void AS $$
19+
DECLARE
20+
v_project_id uuid;
21+
v_worker_user_id uuid;
22+
v_env_user_id uuid;
23+
BEGIN
24+
SELECT project_id, user_id INTO v_project_id, v_worker_user_id
25+
FROM workers
26+
WHERE id = p_worker_id;
27+
28+
IF NOT FOUND THEN
29+
RAISE EXCEPTION 'Worker not found';
30+
END IF;
31+
32+
SELECT user_id INTO v_env_user_id
33+
FROM environments
34+
WHERE id = p_environment_id;
35+
36+
IF NOT FOUND THEN
37+
RAISE EXCEPTION 'Environment not found';
38+
END IF;
39+
40+
IF v_worker_user_id IS DISTINCT FROM v_env_user_id THEN
41+
RAISE EXCEPTION 'Worker and environment must belong to the same user';
42+
END IF;
43+
44+
IF v_project_id IS NOT NULL THEN
45+
-- Worker is in a project: cascade to project + all its workers
46+
UPDATE projects
47+
SET environment_id = p_environment_id, updated_at = now()
48+
WHERE id = v_project_id;
49+
50+
UPDATE workers
51+
SET environment_id = p_environment_id, updated_at = now()
52+
WHERE project_id = v_project_id;
53+
ELSE
54+
-- Standalone worker
55+
UPDATE workers
56+
SET environment_id = p_environment_id, updated_at = now()
57+
WHERE id = p_worker_id;
58+
END IF;
59+
END;
60+
$$ LANGUAGE plpgsql;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- Normalize platform database UUID from aaaa... to nil UUID (00000000-...)
2+
-- The aaaa UUID was arbitrary and fails strict RFC 4122 validation.
3+
-- database_tokens.database_id has ON UPDATE CASCADE so it propagates automatically.
4+
5+
-- 1. Update environment_values that reference this DB (stored as text, no FK)
6+
UPDATE environment_values
7+
SET value = '00000000-0000-0000-0000-000000000000'
8+
WHERE value = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
9+
AND type = 'database';
10+
11+
-- 2. Update the database config itself (cascades to database_tokens)
12+
UPDATE database_configs
13+
SET id = '00000000-0000-0000-0000-000000000000'
14+
WHERE id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
15+

src/backend/api.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::{
22
AssetManifestEntry, Backend, BackendError, CreateDatabaseInput, CreateEnvironmentInput,
33
CreateKvInput, CreateStorageInput, CreateWorkerInput, Database, DeployInput, Deployment,
4-
Environment, KvNamespace, StorageConfig, UpdateEnvironmentInput, UpdateWorkerInput,
4+
Environment, KvNamespace, Project, StorageConfig, UpdateEnvironmentInput, UpdateWorkerInput,
55
UploadResult, Worker,
66
};
77
use crate::config::DEFAULT_API_URL;
@@ -175,6 +175,39 @@ impl Backend for ApiBackend {
175175
Ok(worker)
176176
}
177177

178+
async fn link_worker_environment(
179+
&self,
180+
worker_id: &str,
181+
environment_id: &str,
182+
) -> Result<(), BackendError> {
183+
let response = self
184+
.request(
185+
reqwest::Method::POST,
186+
&format!("/workers/{}/link", worker_id),
187+
)
188+
.json(&serde_json::json!({ "environmentId": environment_id }))
189+
.send()
190+
.await?;
191+
192+
if response.status() == reqwest::StatusCode::NOT_FOUND {
193+
return Err(BackendError::NotFound(format!(
194+
"Worker '{}' or environment '{}' not found",
195+
worker_id, environment_id
196+
)));
197+
}
198+
199+
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
200+
return Err(BackendError::Unauthorized);
201+
}
202+
203+
if !response.status().is_success() {
204+
let text = response.text().await.unwrap_or_default();
205+
return Err(BackendError::Api(text));
206+
}
207+
208+
Ok(())
209+
}
210+
178211
async fn deploy_worker(
179212
&self,
180213
name: &str,
@@ -209,6 +242,7 @@ impl Backend for ApiBackend {
209242
async fn upload_worker(
210243
&self,
211244
name: &str,
245+
_path: &std::path::Path,
212246
zip_data: Vec<u8>,
213247
assets_manifest: &[AssetManifestEntry],
214248
) -> Result<UploadResult, BackendError> {
@@ -259,6 +293,19 @@ impl Backend for ApiBackend {
259293
Ok(result)
260294
}
261295

296+
// Project methods
297+
async fn list_projects(&self) -> Result<Vec<Project>, BackendError> {
298+
Err(BackendError::Api(
299+
"Projects require DB access. Use a DB alias.".to_string(),
300+
))
301+
}
302+
303+
async fn delete_project(&self, _name: &str) -> Result<(), BackendError> {
304+
Err(BackendError::Api(
305+
"Projects require DB access. Use a DB alias.".to_string(),
306+
))
307+
}
308+
262309
async fn list_environments(&self) -> Result<Vec<Environment>, BackendError> {
263310
let response = self
264311
.request(reqwest::Method::GET, "/environments")

0 commit comments

Comments
 (0)