feat: inject provisioned credentials into component deployments
Components now automatically receive DATABASE_URL, REDIS_URL, and other infrastructure credentials when deployed. Previously, credentials were provisioned and stored but never injected into K8s deployments. Changes: - Add fetchProjectCredentials() to component_deploy.go - Populate spec.Secrets before calling deployer.Deploy() - Fix slackpath-4 to provision postgres + redis before services - Add terminology docs to clarify platform vs skeleton code This completes the infrastructure provisioning flow: 1. add-db → provisions CockroachDB, stores DATABASE_URL 2. add-service → deploys with DATABASE_URL in environment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
34e12ff3d5
commit
1e853980e4
10
CLAUDE.md
10
CLAUDE.md
@ -4,6 +4,16 @@ Run Claude Code instances in isolated Kubernetes pods with REST API control. Ena
|
|||||||
|
|
||||||
**Platform:** threesix.ai - Agent-driven development at scale with shared worker pools.
|
**Platform:** threesix.ai - Agent-driven development at scale with shared worker pools.
|
||||||
|
|
||||||
|
## Terminology
|
||||||
|
|
||||||
|
| Term | Meaning | Location |
|
||||||
|
|------|---------|----------|
|
||||||
|
| **platform** | rdev itself (orchestrator API, handlers, workers) | `cmd/rdev-api/`, `internal/`, `pkg/api/` |
|
||||||
|
| **skeleton** | Code that ships in generated projects | `internal/adapter/templates/templates/skeleton/` |
|
||||||
|
| **component templates** | Service/worker/app/cli templates added to skeleton | `templates/components/{service,worker,cli,app-*}/` |
|
||||||
|
|
||||||
|
When discussing code: "add to **platform**" = edit rdev; "add to **skeleton**" = edit project templates.
|
||||||
|
|
||||||
## Find Your Guide
|
## Find Your Guide
|
||||||
|
|
||||||
| If you need to... | Read this |
|
| If you need to... | Read this |
|
||||||
|
|||||||
@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Composable Monorepo Templates evolve rdev's project scaffolding from single templates to full monorepo architecture. Every project starts as a monorepo skeleton, with components (services, workers, apps, cli) added via API calls. Deployment can target the whole monorepo or individual components.
|
Composable Monorepo Templates evolve rdev's project scaffolding from single templates to full monorepo architecture. Every project starts with the **skeleton** (monorepo base), with **component templates** (services, workers, apps, cli) added via API calls. Deployment can target the whole monorepo or individual components.
|
||||||
|
|
||||||
|
**Terminology:** "skeleton" = monorepo base code; "platform" = rdev itself. See [terminology.md](../terminology.md).
|
||||||
|
|
||||||
**Key Facts:**
|
**Key Facts:**
|
||||||
- `POST /projects` creates monorepo skeleton (not single template)
|
- `POST /projects` creates monorepo skeleton (not single template)
|
||||||
|
|||||||
@ -4,6 +4,8 @@ Quick reference for rdev concepts and facts.
|
|||||||
|
|
||||||
| Topic | File | Confidence | Updated | Summary |
|
| Topic | File | Confidence | Updated | Summary |
|
||||||
|-------|------|------------|---------|---------|
|
|-------|------|------------|---------|---------|
|
||||||
|
| **Terminology** |
|
||||||
|
| Platform vs Skeleton | [terminology.md](./terminology.md) | High | 2026-02 | Distinguishing rdev code from generated project code |
|
||||||
| **Architecture** |
|
| **Architecture** |
|
||||||
| Hexagonal Architecture | [patterns/hexagonal.md](./patterns/hexagonal.md) | High | 2025-01 | Ports/adapters pattern, layer separation |
|
| Hexagonal Architecture | [patterns/hexagonal.md](./patterns/hexagonal.md) | High | 2025-01 | Ports/adapters pattern, layer separation |
|
||||||
| **Core Services** |
|
| **Core Services** |
|
||||||
|
|||||||
35
ai-lookup/terminology.md
Normal file
35
ai-lookup/terminology.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# rdev Terminology
|
||||||
|
|
||||||
|
**Last Updated:** 2026-02-05
|
||||||
|
**Confidence:** High
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Distinguishing between rdev code (the platform) and generated project code (skeleton) prevents confusion when discussing implementations.
|
||||||
|
|
||||||
|
## Core Terms
|
||||||
|
|
||||||
|
| Term | Definition | Code Location |
|
||||||
|
|------|------------|---------------|
|
||||||
|
| **platform** | rdev API itself—handlers, services, adapters, workers | `cmd/rdev-api/`, `internal/`, `pkg/api/` |
|
||||||
|
| **skeleton** | Monorepo base that ships in every generated project | `templates/skeleton/` |
|
||||||
|
| **component templates** | Templates for services, workers, apps, CLIs added to skeleton | `templates/components/` |
|
||||||
|
| **generated project** | A project created by rdev (contains skeleton + components) | Lives in Gitea repos |
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
- "Add auth middleware to **platform**" → edit `internal/auth/`
|
||||||
|
- "Add auth middleware to **skeleton**" → edit `templates/skeleton/pkg/auth/`
|
||||||
|
- "Platform's work queue" → `internal/adapter/postgres/work_queue.go`
|
||||||
|
- "Skeleton's job queue" → `templates/skeleton/pkg/queue/` (planned)
|
||||||
|
|
||||||
|
## File Pointers
|
||||||
|
|
||||||
|
- Platform entry: `cmd/rdev-api/main.go`
|
||||||
|
- Skeleton root: `internal/adapter/templates/templates/skeleton/`
|
||||||
|
- Component registry: `internal/adapter/templates/registry.go`
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Composable Monorepo](./features/composable-monorepo.md) - Skeleton + component architecture
|
||||||
|
- [Template Provider](./services/template-provider.md) - How templates are seeded
|
||||||
@ -19,19 +19,42 @@ steps:
|
|||||||
- project_id: .data.name
|
- project_id: .data.name
|
||||||
- domain: .data.domain
|
- domain: .data.domain
|
||||||
|
|
||||||
|
add-db:
|
||||||
|
description: Add CockroachDB for user/auth storage
|
||||||
|
depends_on: [create-project]
|
||||||
|
action: api
|
||||||
|
method: POST
|
||||||
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
|
body:
|
||||||
|
type: postgres
|
||||||
|
name: "main-db"
|
||||||
|
|
||||||
|
add-redis:
|
||||||
|
description: Add Redis for job queue and pub/sub
|
||||||
|
depends_on: [create-project]
|
||||||
|
action: api
|
||||||
|
method: POST
|
||||||
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
|
body:
|
||||||
|
type: redis
|
||||||
|
name: "job-queue"
|
||||||
|
|
||||||
add-auth:
|
add-auth:
|
||||||
|
depends_on: [add-db]
|
||||||
action: api
|
action: api
|
||||||
method: POST
|
method: POST
|
||||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
body: { type: service, name: "auth-svc" }
|
body: { type: service, name: "auth-svc" }
|
||||||
|
|
||||||
add-chat:
|
add-chat:
|
||||||
|
depends_on: [add-redis]
|
||||||
action: api
|
action: api
|
||||||
method: POST
|
method: POST
|
||||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
body: { type: service, name: "chat-svc" }
|
body: { type: service, name: "chat-svc" }
|
||||||
|
|
||||||
add-worker:
|
add-worker:
|
||||||
|
depends_on: [add-redis]
|
||||||
action: api
|
action: api
|
||||||
method: POST
|
method: POST
|
||||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
|
|||||||
@ -32,6 +32,9 @@ func (s *ComponentService) createInitialComponentDeployment(
|
|||||||
// Build sibling service URLs for service discovery
|
// Build sibling service URLs for service discovery
|
||||||
siblingServices := s.buildSiblingServiceURLs(ctx, projectID, component.Name)
|
siblingServices := s.buildSiblingServiceURLs(ctx, projectID, component.Name)
|
||||||
|
|
||||||
|
// Fetch project credentials (DATABASE_URL, REDIS_URL, etc.) from credential store
|
||||||
|
secrets := s.fetchProjectCredentials(ctx, projectID)
|
||||||
|
|
||||||
spec := domain.DeploySpec{
|
spec := domain.DeploySpec{
|
||||||
ProjectName: projectID,
|
ProjectName: projectID,
|
||||||
ComponentPath: component.Path,
|
ComponentPath: component.Path,
|
||||||
@ -41,6 +44,7 @@ func (s *ComponentService) createInitialComponentDeployment(
|
|||||||
Replicas: 1,
|
Replicas: 1,
|
||||||
BasePath: basePath,
|
BasePath: basePath,
|
||||||
SiblingServices: siblingServices,
|
SiblingServices: siblingServices,
|
||||||
|
Secrets: secrets,
|
||||||
}
|
}
|
||||||
|
|
||||||
log := logging.FromContext(ctx).WithService("component")
|
log := logging.FromContext(ctx).WithService("component")
|
||||||
@ -152,3 +156,58 @@ func (s *ComponentService) buildSiblingServiceURLs(ctx context.Context, projectI
|
|||||||
func toUpperSnake(s string) string {
|
func toUpperSnake(s string) string {
|
||||||
return strings.ToUpper(strings.ReplaceAll(s, "-", "_"))
|
return strings.ToUpper(strings.ReplaceAll(s, "-", "_"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetchProjectCredentials retrieves stored infrastructure credentials for a project.
|
||||||
|
// Returns a map of env var names to values (e.g., {"DATABASE_URL": "postgres://...", "REDIS_URL": "redis://..."}).
|
||||||
|
// Missing credentials are silently skipped - not all projects have all infrastructure.
|
||||||
|
func (s *ComponentService) fetchProjectCredentials(ctx context.Context, projectID string) map[string]string {
|
||||||
|
if s.credentialStore == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of infrastructure credentials to fetch
|
||||||
|
credentialKeys := []string{
|
||||||
|
"DATABASE_URL",
|
||||||
|
"DATABASE_URL_STAGING",
|
||||||
|
"REDIS_URL",
|
||||||
|
"REDIS_URL_STAGING",
|
||||||
|
"REDIS_PREFIX",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build scoped keys: "{projectID}:{key}"
|
||||||
|
scopedKeys := make([]string, len(credentialKeys))
|
||||||
|
for i, key := range credentialKeys {
|
||||||
|
scopedKeys[i] = projectID + ":" + key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all credentials in one call
|
||||||
|
creds, err := s.credentialStore.GetMultiple(ctx, scopedKeys)
|
||||||
|
if err != nil {
|
||||||
|
log := logging.FromContext(ctx).WithService("component")
|
||||||
|
log.Warn("failed to fetch project credentials",
|
||||||
|
logging.FieldProjectID, projectID,
|
||||||
|
logging.FieldError, err,
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert scoped keys back to env var names
|
||||||
|
secrets := make(map[string]string)
|
||||||
|
for scopedKey, value := range creds {
|
||||||
|
// Extract env var name from scoped key: "myproject:DATABASE_URL" -> "DATABASE_URL"
|
||||||
|
parts := strings.SplitN(scopedKey, ":", 2)
|
||||||
|
if len(parts) == 2 && value != "" {
|
||||||
|
secrets[parts[1]] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(secrets) > 0 {
|
||||||
|
log := logging.FromContext(ctx).WithService("component")
|
||||||
|
log.Debug("fetched project credentials for deployment",
|
||||||
|
logging.FieldProjectID, projectID,
|
||||||
|
"credential_count", len(secrets),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return secrets
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user