Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ stacker init --with-ai # no Ollama running → template fallback
#### REST API Routes (`/project/{id}/apps/*`)
- `GET /project/{id}/apps` - List all apps for a project
- `GET /project/{id}/apps/{code}` - Get single app details
- `DELETE /project/{id}/apps/{code}` - Delete a saved app from a project
- `GET /project/{id}/apps/{code}/config` - Get full app configuration
- `GET /project/{id}/apps/{code}/env` - Get environment variables (sensitive values redacted)
- `PUT /project/{id}/apps/{code}/env` - Update environment variables
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ cargo run --bin server # http://127.0.0.1:8000
| `POST /project` | Create a project from a stack definition |
| `POST /{id}/deploy/{cloud_id}` | Deploy to a cloud provider |
| `GET /project/{id}/apps` | List apps in a project |
| `DELETE /project/{id}/apps/{code}` | Remove an app from a project |
| `PUT /project/{id}/apps/{code}/env` | Update app environment variables |
| `GET /project/{id}/apps/{code}/secrets` | List service-scoped secret metadata for an app |
| `PUT /project/{id}/apps/{code}/secrets/{name}` | Create or update a Vault-backed service secret |
Expand Down
10 changes: 10 additions & 0 deletions migrations/20260717120017_casbin_project_app_delete.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Remove Casbin rules for deleting project apps

DELETE FROM public.casbin_rule
WHERE ptype = 'p'
AND v0 = 'group_user'
AND v1 IN (
'/project/:id/apps/:code',
'/api/v1/project/:id/apps/:code'
)
AND v2 = 'DELETE';
7 changes: 7 additions & 0 deletions migrations/20260717120017_casbin_project_app_delete.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Add Casbin rules for deleting project apps

INSERT INTO public.casbin_rule (ptype, v0, v1, v2, v3, v4, v5)
VALUES
('p', 'group_user', '/project/:id/apps/:code', 'DELETE', '', '', ''),
('p', 'group_user', '/api/v1/project/:id/apps/:code', 'DELETE', '', '', '')
ON CONFLICT DO NOTHING;
44 changes: 29 additions & 15 deletions src/bin/stacker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,8 +609,10 @@ enum SecretsCommands {
Local .env secret:\n\
stacker secrets set DB_PASSWORD=supersecret\n\
\n\
Remote service secret (project.identity from stacker.yml):\n\
Remote deployable service/app target secret (project.identity from stacker.yml):\n\
stacker secrets set S3_SECRET_KEY --scope service --service uploader --body supersecret\n\
\n\
Use the target code listed by `stacker secrets apps` for --service.\n\
\n\
Remote server secret from stdin:\n\
cat token.txt | stacker secrets set NPM_TOKEN --scope server --server-id 42")]
Expand All @@ -630,8 +632,8 @@ enum SecretsCommands {
/// Project name or ID for service-scoped secrets (defaults to project.identity in stacker.yml)
#[arg(long, value_name = "PROJECT", requires = "scope")]
project: Option<String>,
/// App code for service-scoped secrets
#[arg(long, value_name = "APP_CODE", requires = "scope")]
/// Deployable service/app target code listed by `stacker secrets apps`
#[arg(long, value_name = "TARGET_CODE", requires = "scope")]
service: Option<String>,
/// Server ID for server-scoped secrets
#[arg(long, value_name = "SERVER_ID", requires = "scope")]
Expand Down Expand Up @@ -684,8 +686,8 @@ Remote get is metadata-only in v1 and does not reveal plaintext values.")]
/// Project name or ID for service-scoped secrets (defaults to project.identity in stacker.yml)
#[arg(long, value_name = "PROJECT", requires = "scope")]
project: Option<String>,
/// App code for service-scoped secrets
#[arg(long, value_name = "APP_CODE", requires = "scope")]
/// Deployable service/app target code listed by `stacker secrets apps`
#[arg(long, value_name = "TARGET_CODE", requires = "scope")]
service: Option<String>,
/// Server ID for server-scoped secrets
#[arg(long, value_name = "SERVER_ID", requires = "scope")]
Expand Down Expand Up @@ -723,8 +725,8 @@ Remote get is metadata-only in v1 and does not reveal plaintext values.")]
/// Project name or ID for service-scoped secrets (defaults to project.identity in stacker.yml)
#[arg(long, value_name = "PROJECT", requires = "scope")]
project: Option<String>,
/// App code for service-scoped secrets
#[arg(long, value_name = "APP_CODE", requires = "scope")]
/// Deployable service/app target code listed by `stacker secrets apps`
#[arg(long, value_name = "TARGET_CODE", requires = "scope")]
service: Option<String>,
/// Server ID for server-scoped secrets
#[arg(long, value_name = "SERVER_ID", requires = "scope")]
Expand All @@ -733,16 +735,16 @@ Remote get is metadata-only in v1 and does not reveal plaintext values.")]
#[arg(long, requires = "scope")]
json: bool,
},
/// List valid remote app codes for a project
/// List valid remote deployable service/app target codes (`stacker secrets apps`)
#[command(
visible_alias = "services",
after_help = "Examples:\n\
List remote app codes using project.identity from stacker.yml:\n\
List remote target codes using project.identity from stacker.yml:\n\
stacker secrets apps\n\
\n\
List remote app codes for a project:\n\
\n\
List remote target codes for a project:\n\
stacker secrets apps --project blog\n\
\n\
\n\
Output app metadata as JSON:\n\
stacker secrets apps --json"
)]
Expand Down Expand Up @@ -780,8 +782,8 @@ Remote get is metadata-only in v1 and does not reveal plaintext values.")]
/// Project name or ID for service-scoped secrets (defaults to project.identity in stacker.yml)
#[arg(long, value_name = "PROJECT", requires = "scope")]
project: Option<String>,
/// App code for service-scoped secrets
#[arg(long, value_name = "APP_CODE", requires = "scope")]
/// Deployable service/app target code listed by `stacker secrets apps`
#[arg(long, value_name = "TARGET_CODE", requires = "scope")]
service: Option<String>,
/// Server ID for server-scoped secrets
#[arg(long, value_name = "SERVER_ID", requires = "scope")]
Expand Down Expand Up @@ -2242,7 +2244,19 @@ mod tests {
assert!(help.contains("--scope service"));
assert!(help.contains("--scope server"));
assert!(help.contains("metadata-only"));
assert!(help.contains("List valid remote app codes for a project"));
assert!(help.contains("List valid remote deployable service/app target codes"));
}

#[test]
fn test_secrets_help_describes_service_scope_as_deployable_target() {
let mut command = Cli::command();
let secrets = command
.find_subcommand_mut("secrets")
.expect("secrets subcommand should exist");
let help = render_command_help(secrets);

assert!(help.contains("deployable service/app target"));
assert!(help.contains("stacker secrets apps"));
}

#[test]
Expand Down
Loading
Loading