nvoi 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -5
- data/Gemfile.lock +17 -8
- data/Rakefile +1 -1
- data/lib/nvoi/cli/config/command.rb +46 -41
- data/lib/nvoi/cli/credentials/edit/command.rb +20 -20
- data/lib/nvoi/cli/credentials/show/command.rb +1 -1
- data/lib/nvoi/cli/db/command.rb +10 -10
- data/lib/nvoi/cli/delete/command.rb +2 -2
- data/lib/nvoi/cli/deploy/command.rb +29 -13
- data/lib/nvoi/cli/deploy/steps/build_image.rb +48 -6
- data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +2 -2
- data/lib/nvoi/cli/deploy/steps/deploy_service.rb +3 -13
- data/lib/nvoi/cli/deploy/steps/provision_server.rb +1 -1
- data/lib/nvoi/cli/deploy/steps/provision_volume.rb +1 -1
- data/lib/nvoi/cli/exec/command.rb +3 -3
- data/lib/nvoi/cli/logs/command.rb +2 -2
- data/lib/nvoi/cli/onboard/command.rb +176 -622
- data/lib/nvoi/cli/onboard/steps/app.rb +108 -0
- data/lib/nvoi/cli/onboard/steps/app_name.rb +26 -0
- data/lib/nvoi/cli/onboard/steps/compute.rb +139 -0
- data/lib/nvoi/cli/onboard/steps/database.rb +97 -0
- data/lib/nvoi/cli/onboard/steps/domain.rb +48 -0
- data/lib/nvoi/cli/onboard/steps/env.rb +67 -0
- data/lib/nvoi/cli/onboard/ui.rb +84 -0
- data/lib/nvoi/cli/unlock/command.rb +2 -2
- data/lib/nvoi/cli.rb +0 -32
- data/lib/nvoi/configuration/app_service.rb +54 -0
- data/lib/nvoi/configuration/application.rb +44 -0
- data/lib/nvoi/configuration/builder.rb +417 -0
- data/lib/nvoi/configuration/database.rb +56 -0
- data/lib/nvoi/configuration/deploy.rb +15 -0
- data/lib/nvoi/{objects/service_spec.rb → configuration/deployment.rb} +4 -3
- data/lib/nvoi/{objects/config_override.rb → configuration/override.rb} +4 -4
- data/lib/nvoi/configuration/providers.rb +78 -0
- data/lib/nvoi/configuration/result.rb +43 -0
- data/lib/nvoi/configuration/root.rb +234 -0
- data/lib/nvoi/configuration/server.rb +39 -0
- data/lib/nvoi/configuration/service.rb +62 -0
- data/lib/nvoi/external/cloud/aws.rb +12 -12
- data/lib/nvoi/external/cloud/hetzner.rb +7 -7
- data/lib/nvoi/external/cloud/scaleway.rb +7 -7
- data/lib/nvoi/external/cloud/types.rb +42 -0
- data/lib/nvoi/external/containerd.rb +1 -48
- data/lib/nvoi/external/database/mysql.rb +1 -1
- data/lib/nvoi/external/database/postgres.rb +1 -1
- data/lib/nvoi/external/database/provider.rb +1 -1
- data/lib/nvoi/external/database/sqlite.rb +1 -1
- data/lib/nvoi/external/database/types.rb +55 -0
- data/lib/nvoi/external/dns/cloudflare.rb +6 -6
- data/lib/nvoi/external/dns/types.rb +24 -0
- data/lib/nvoi/external/ssh.rb +0 -12
- data/lib/nvoi/external/ssh_tunnel.rb +100 -0
- data/lib/nvoi/utils/config_loader.rb +12 -12
- data/lib/nvoi/utils/credential_store.rb +4 -4
- data/lib/nvoi/utils/env_resolver.rb +3 -3
- data/lib/nvoi/utils/namer.rb +2 -2
- data/lib/nvoi/utils/presence.rb +23 -0
- data/lib/nvoi/version.rb +1 -1
- data/lib/nvoi.rb +2 -17
- metadata +96 -57
- data/.claude/todo/refactor/00-overview.md +0 -171
- data/.claude/todo/refactor/01-objects.md +0 -96
- data/.claude/todo/refactor/02-utils.md +0 -143
- data/.claude/todo/refactor/03-external-cloud.md +0 -164
- data/.claude/todo/refactor/04-external-dns.md +0 -104
- data/.claude/todo/refactor/05-external.md +0 -133
- data/.claude/todo/refactor/06-cli.md +0 -123
- data/.claude/todo/refactor/07-cli-deploy-command.md +0 -177
- data/.claude/todo/refactor/08-cli-deploy-steps.md +0 -201
- data/.claude/todo/refactor/09-cli-delete-command.md +0 -169
- data/.claude/todo/refactor/10-cli-exec-command.md +0 -157
- data/.claude/todo/refactor/11-cli-credentials-command.md +0 -190
- data/.claude/todo/refactor/12-cli-db-command.md +0 -128
- data/.claude/todo/refactor/_target.md +0 -79
- data/.claude/todo/refactor-execution/00-entrypoint.md +0 -49
- data/.claude/todo/refactor-execution/01-objects.md +0 -42
- data/.claude/todo/refactor-execution/02-utils.md +0 -41
- data/.claude/todo/refactor-execution/03-external-cloud.md +0 -38
- data/.claude/todo/refactor-execution/04-external-dns.md +0 -35
- data/.claude/todo/refactor-execution/05-external-other.md +0 -46
- data/.claude/todo/refactor-execution/06-cli-deploy.md +0 -45
- data/.claude/todo/refactor-execution/07-cli-delete.md +0 -43
- data/.claude/todo/refactor-execution/08-cli-exec.md +0 -30
- data/.claude/todo/refactor-execution/09-cli-credentials.md +0 -34
- data/.claude/todo/refactor-execution/10-cli-db.md +0 -31
- data/.claude/todo/refactor-execution/11-cli-router.md +0 -44
- data/.claude/todo/refactor-execution/12-cleanup.md +0 -120
- data/.claude/todo/refactor-execution/_monitoring-strategy.md +0 -126
- data/.claude/todo/scaleway.impl.md +0 -644
- data/.claude/todo/scaleway.reference.md +0 -520
- data/.claude/todos.md +0 -550
- data/ingest +0 -0
- data/lib/nvoi/config_api/actions/app.rb +0 -53
- data/lib/nvoi/config_api/actions/compute_provider.rb +0 -55
- data/lib/nvoi/config_api/actions/database.rb +0 -70
- data/lib/nvoi/config_api/actions/domain_provider.rb +0 -40
- data/lib/nvoi/config_api/actions/env.rb +0 -32
- data/lib/nvoi/config_api/actions/init.rb +0 -67
- data/lib/nvoi/config_api/actions/secret.rb +0 -32
- data/lib/nvoi/config_api/actions/server.rb +0 -66
- data/lib/nvoi/config_api/actions/service.rb +0 -52
- data/lib/nvoi/config_api/actions/volume.rb +0 -40
- data/lib/nvoi/config_api/base.rb +0 -38
- data/lib/nvoi/config_api/result.rb +0 -26
- data/lib/nvoi/config_api.rb +0 -93
- data/lib/nvoi/objects/configuration.rb +0 -483
- data/lib/nvoi/objects/database.rb +0 -56
- data/lib/nvoi/objects/dns.rb +0 -14
- data/lib/nvoi/objects/firewall.rb +0 -11
- data/lib/nvoi/objects/network.rb +0 -11
- data/lib/nvoi/objects/server.rb +0 -14
- data/lib/nvoi/objects/tunnel.rb +0 -14
- data/lib/nvoi/objects/volume.rb +0 -17
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
# 03 - External: Cloud Providers
|
|
2
|
-
|
|
3
|
-
## Priority: THIRD
|
|
4
|
-
|
|
5
|
-
External adapters depend on Objects and Utils.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Current State
|
|
10
|
-
|
|
11
|
-
| File | Lines | Purpose |
|
|
12
|
-
| ------------------------------ | ----- | ---------------------------- |
|
|
13
|
-
| `providers/base.rb` | 112 | Abstract interface + Structs |
|
|
14
|
-
| `providers/hetzner.rb` | 289 | Hetzner Cloud implementation |
|
|
15
|
-
| `providers/hetzner_client.rb` | ~200 | Hetzner HTTP client |
|
|
16
|
-
| `providers/aws.rb` | ~250 | AWS implementation |
|
|
17
|
-
| `providers/scaleway.rb` | ~280 | Scaleway implementation |
|
|
18
|
-
| `providers/scaleway_client.rb` | ~200 | Scaleway HTTP client |
|
|
19
|
-
| `service/provider.rb` | ~50 | Provider factory helper |
|
|
20
|
-
|
|
21
|
-
**Total: ~1380 lines across 7 files**
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Target Structure
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
lib/nvoi/external/cloud/
|
|
29
|
-
├── base.rb # Abstract interface (methods only, no Structs)
|
|
30
|
-
├── hetzner.rb # Hetzner (merge provider + client)
|
|
31
|
-
├── aws.rb # AWS
|
|
32
|
-
├── scaleway.rb # Scaleway (merge provider + client)
|
|
33
|
-
└── factory.rb # Provider initialization helper
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## What Each Provider Does
|
|
39
|
-
|
|
40
|
-
All providers implement:
|
|
41
|
-
|
|
42
|
-
```ruby
|
|
43
|
-
# Network operations
|
|
44
|
-
find_or_create_network(name)
|
|
45
|
-
get_network_by_name(name)
|
|
46
|
-
delete_network(id)
|
|
47
|
-
|
|
48
|
-
# Firewall operations
|
|
49
|
-
find_or_create_firewall(name)
|
|
50
|
-
get_firewall_by_name(name)
|
|
51
|
-
delete_firewall(id)
|
|
52
|
-
|
|
53
|
-
# Server operations
|
|
54
|
-
find_server(name)
|
|
55
|
-
list_servers
|
|
56
|
-
create_server(opts)
|
|
57
|
-
wait_for_server(server_id, max_attempts)
|
|
58
|
-
delete_server(id)
|
|
59
|
-
|
|
60
|
-
# Volume operations
|
|
61
|
-
create_volume(opts)
|
|
62
|
-
get_volume(id)
|
|
63
|
-
get_volume_by_name(name)
|
|
64
|
-
delete_volume(id)
|
|
65
|
-
attach_volume(volume_id, server_id)
|
|
66
|
-
detach_volume(volume_id)
|
|
67
|
-
|
|
68
|
-
# Validation
|
|
69
|
-
validate_instance_type(instance_type)
|
|
70
|
-
validate_region(region)
|
|
71
|
-
validate_credentials
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## DRY Opportunities
|
|
77
|
-
|
|
78
|
-
### 1. Merge Provider + Client
|
|
79
|
-
|
|
80
|
-
Current pattern:
|
|
81
|
-
|
|
82
|
-
- `hetzner.rb` = high-level operations
|
|
83
|
-
- `hetzner_client.rb` = HTTP calls
|
|
84
|
-
|
|
85
|
-
Merge into single file. The "client" is just private methods:
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
# external/cloud/hetzner.rb
|
|
89
|
-
module Nvoi
|
|
90
|
-
module External
|
|
91
|
-
module Cloud
|
|
92
|
-
class Hetzner < Base
|
|
93
|
-
def find_server(name) ... end # public
|
|
94
|
-
|
|
95
|
-
private
|
|
96
|
-
|
|
97
|
-
def get(path) ... end # HTTP helpers
|
|
98
|
-
def post(path, body) ... end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### 2. Extract Common HTTP Client
|
|
106
|
-
|
|
107
|
-
All providers do similar HTTP work. Extract base:
|
|
108
|
-
|
|
109
|
-
```ruby
|
|
110
|
-
# external/cloud/base.rb
|
|
111
|
-
class Base
|
|
112
|
-
private
|
|
113
|
-
|
|
114
|
-
def http_get(url, headers = {}) ... end
|
|
115
|
-
def http_post(url, body, headers = {}) ... end
|
|
116
|
-
def handle_response(response) ... end
|
|
117
|
-
end
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### 3. Provider Factory
|
|
121
|
-
|
|
122
|
-
Current `service/provider.rb` has `ProviderHelper` module. Move to:
|
|
123
|
-
|
|
124
|
-
```ruby
|
|
125
|
-
# external/cloud/factory.rb
|
|
126
|
-
module Nvoi
|
|
127
|
-
module External
|
|
128
|
-
module Cloud
|
|
129
|
-
def self.for(config)
|
|
130
|
-
case config.provider_name
|
|
131
|
-
when "hetzner" then Hetzner.new(config.hetzner.api_token)
|
|
132
|
-
when "aws" then AWS.new(config.aws)
|
|
133
|
-
when "scaleway" then Scaleway.new(config.scaleway)
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### 4. Struct Extraction (already in 01-objects.md)
|
|
142
|
-
|
|
143
|
-
Remove `Server`, `Network`, etc. from `base.rb` → use from `objects/`
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## Migration Steps
|
|
148
|
-
|
|
149
|
-
1. Create `lib/nvoi/external/cloud/` directory
|
|
150
|
-
2. Create `base.rb` with interface only (no Structs)
|
|
151
|
-
3. Merge `hetzner.rb` + `hetzner_client.rb` → `external/cloud/hetzner.rb`
|
|
152
|
-
4. Move `aws.rb` → `external/cloud/aws.rb`
|
|
153
|
-
5. Merge `scaleway.rb` + `scaleway_client.rb` → `external/cloud/scaleway.rb`
|
|
154
|
-
6. Move `service/provider.rb` → `external/cloud/factory.rb`
|
|
155
|
-
7. Update requires to use `Objects::Server`, etc.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## Estimated Effort
|
|
160
|
-
|
|
161
|
-
- **Lines to consolidate:** ~1380
|
|
162
|
-
- **Files created:** 5
|
|
163
|
-
- **Files deleted:** 7
|
|
164
|
-
- **DRY savings:** ~100 lines (merged clients, shared HTTP base)
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# 04 - External: DNS Provider (Cloudflare)
|
|
2
|
-
|
|
3
|
-
## Priority: THIRD (parallel with cloud)
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Current State
|
|
8
|
-
|
|
9
|
-
| File | Lines | Purpose |
|
|
10
|
-
| ---------------------------- | ----- | -------------------------- |
|
|
11
|
-
| `cloudflare/client.rb` | 288 | Cloudflare API client |
|
|
12
|
-
| `deployer/tunnel_manager.rb` | 58 | Tunnel setup orchestration |
|
|
13
|
-
|
|
14
|
-
**Total: ~346 lines across 2 files**
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Target Structure
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
lib/nvoi/external/dns/
|
|
22
|
-
└── cloudflare.rb # Full Cloudflare client (tunnels + DNS)
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## What Cloudflare Client Does
|
|
28
|
-
|
|
29
|
-
### Tunnel Operations
|
|
30
|
-
|
|
31
|
-
- `create_tunnel(name)` → Tunnel
|
|
32
|
-
- `find_tunnel(name)` → Tunnel | nil
|
|
33
|
-
- `get_tunnel_token(tunnel_id)` → String
|
|
34
|
-
- `update_tunnel_configuration(tunnel_id, hostname, service_url)`
|
|
35
|
-
- `verify_tunnel_configuration(tunnel_id, hostname, service_url, attempts)`
|
|
36
|
-
- `delete_tunnel(tunnel_id)`
|
|
37
|
-
|
|
38
|
-
### DNS Operations
|
|
39
|
-
|
|
40
|
-
- `find_zone(domain)` → Zone | nil
|
|
41
|
-
- `find_dns_record(zone_id, name, type)` → DNSRecord | nil
|
|
42
|
-
- `create_dns_record(zone_id, name, type, content, proxied:)`
|
|
43
|
-
- `update_dns_record(zone_id, record_id, name, type, content, proxied:)`
|
|
44
|
-
- `create_or_update_dns_record(...)` → convenience method
|
|
45
|
-
- `delete_dns_record(zone_id, record_id)`
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## DRY Opportunities
|
|
50
|
-
|
|
51
|
-
### 1. Absorb TunnelManager into Cloudflare Client
|
|
52
|
-
|
|
53
|
-
`TunnelManager.setup_tunnel` does:
|
|
54
|
-
|
|
55
|
-
1. Find or create tunnel
|
|
56
|
-
2. Get token
|
|
57
|
-
3. Configure ingress
|
|
58
|
-
4. Verify configuration
|
|
59
|
-
5. Create DNS record
|
|
60
|
-
|
|
61
|
-
This is just a convenience method. Move into client:
|
|
62
|
-
|
|
63
|
-
```ruby
|
|
64
|
-
# external/dns/cloudflare.rb
|
|
65
|
-
class Cloudflare
|
|
66
|
-
# Existing low-level methods...
|
|
67
|
-
|
|
68
|
-
# High-level convenience
|
|
69
|
-
def setup_tunnel(name, hostname, service_url, domain)
|
|
70
|
-
tunnel = find_tunnel(name) || create_tunnel(name)
|
|
71
|
-
token = tunnel.token || get_tunnel_token(tunnel.id)
|
|
72
|
-
update_tunnel_configuration(tunnel.id, hostname, service_url)
|
|
73
|
-
verify_tunnel_configuration(tunnel.id, hostname, service_url, Constants::TUNNEL_CONFIG_VERIFY_ATTEMPTS)
|
|
74
|
-
|
|
75
|
-
zone = find_zone(domain)
|
|
76
|
-
create_or_update_dns_record(zone.id, hostname, "CNAME", "#{tunnel.id}.cfargotunnel.com")
|
|
77
|
-
|
|
78
|
-
Objects::TunnelInfo.new(tunnel_id: tunnel.id, tunnel_token: token)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### 2. Remove Struct Definitions
|
|
84
|
-
|
|
85
|
-
Move `Tunnel`, `Zone`, `DNSRecord` to `objects/` (done in 01-objects.md)
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## Migration Steps
|
|
90
|
-
|
|
91
|
-
1. Create `lib/nvoi/external/dns/` directory
|
|
92
|
-
2. Move `cloudflare/client.rb` → `external/dns/cloudflare.rb`
|
|
93
|
-
3. Merge `deployer/tunnel_manager.rb` into Cloudflare as `setup_tunnel` method
|
|
94
|
-
4. Remove Struct definitions (use from `objects/`)
|
|
95
|
-
5. Update namespace from `Cloudflare::Client` to `External::DNS::Cloudflare`
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Estimated Effort
|
|
100
|
-
|
|
101
|
-
- **Lines to consolidate:** ~346
|
|
102
|
-
- **Files created:** 1
|
|
103
|
-
- **Files deleted:** 2
|
|
104
|
-
- **DRY savings:** ~20 lines (TunnelManager overhead)
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
# 05 - External: SSH, Kubectl, Containerd
|
|
2
|
-
|
|
3
|
-
## Priority: THIRD (parallel with cloud/dns)
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Current State
|
|
8
|
-
|
|
9
|
-
| File | Lines | Purpose |
|
|
10
|
-
| -------------------------- | ----- | ---------------------------- |
|
|
11
|
-
| `remote/ssh_executor.rb` | 73 | SSH command execution |
|
|
12
|
-
| `remote/docker_manager.rb` | 204 | Docker/containerd operations |
|
|
13
|
-
| `remote/volume_manager.rb` | 104 | Volume mount operations |
|
|
14
|
-
| `k8s/renderer.rb` | 45 | kubectl apply wrapper |
|
|
15
|
-
|
|
16
|
-
**Total: ~426 lines across 4 files**
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Target Structure
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
lib/nvoi/external/
|
|
24
|
-
├── ssh.rb # SSH executor
|
|
25
|
-
├── kubectl.rb # kubectl wrapper (from k8s/renderer.rb)
|
|
26
|
-
└── containerd.rb # containerd/Docker operations (from docker_manager.rb + volume_manager.rb)
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## What Each Does
|
|
32
|
-
|
|
33
|
-
### SSH Executor (`remote/ssh_executor.rb`)
|
|
34
|
-
|
|
35
|
-
- `execute(command, stream: false)` → String
|
|
36
|
-
- `execute_quiet(command)` → nil (ignore errors)
|
|
37
|
-
- `open_shell` → exec into SSH
|
|
38
|
-
|
|
39
|
-
### Docker Manager (`remote/docker_manager.rb`)
|
|
40
|
-
|
|
41
|
-
- `create_network(name)`
|
|
42
|
-
- `build_image(path, tag, cache_from)` → local build, rsync, ctr import
|
|
43
|
-
- `run_container(opts)`
|
|
44
|
-
- `exec(container, command)`
|
|
45
|
-
- `wait_for_health(opts)`
|
|
46
|
-
- `container_status(name)`, `container_logs(name, lines)`
|
|
47
|
-
- `stop_container(name)`, `remove_container(name)`
|
|
48
|
-
- `list_containers(filter)`, `list_images(filter)`
|
|
49
|
-
- `cleanup_old_images(prefix, keep_tags)`
|
|
50
|
-
- `container_running?(name)`
|
|
51
|
-
- `setup_cloudflared(network, token, name)`
|
|
52
|
-
|
|
53
|
-
### Volume Manager (`remote/volume_manager.rb`)
|
|
54
|
-
|
|
55
|
-
- `mount(opts)` → format + mount + fstab
|
|
56
|
-
- `unmount(mount_path)`
|
|
57
|
-
- `mounted?(mount_path)`
|
|
58
|
-
- `remove_from_fstab(mount_path)`
|
|
59
|
-
|
|
60
|
-
### K8s Renderer (`k8s/renderer.rb`)
|
|
61
|
-
|
|
62
|
-
- `render_template(name, data)` → String
|
|
63
|
-
- `apply_manifest(ssh, template_name, data)` → kubectl apply
|
|
64
|
-
- `wait_for_deployment(ssh, name, namespace, timeout)`
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## DRY Opportunities
|
|
69
|
-
|
|
70
|
-
### 1. Rename docker_manager → containerd
|
|
71
|
-
|
|
72
|
-
It's not really Docker anymore. It uses `ctr` (containerd). Name it accurately:
|
|
73
|
-
|
|
74
|
-
```ruby
|
|
75
|
-
# external/containerd.rb
|
|
76
|
-
class Containerd
|
|
77
|
-
def initialize(ssh) ... end
|
|
78
|
-
def build_and_import(path, tag) ... end
|
|
79
|
-
def list_images(filter) ... end
|
|
80
|
-
def cleanup_images(prefix, keep_tags) ... end
|
|
81
|
-
end
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### 2. Extract Volume Logic
|
|
85
|
-
|
|
86
|
-
Volume mounting is separate from container runtime. Could stay in containerd or be separate.
|
|
87
|
-
Decision: Keep in containerd since it's all "server-side stuff via SSH".
|
|
88
|
-
|
|
89
|
-
### 3. Simplify K8s Renderer
|
|
90
|
-
|
|
91
|
-
Move template rendering to `utils/templates.rb`. Keep only kubectl operations:
|
|
92
|
-
|
|
93
|
-
```ruby
|
|
94
|
-
# external/kubectl.rb
|
|
95
|
-
class Kubectl
|
|
96
|
-
def initialize(ssh) ... end
|
|
97
|
-
def apply(manifest) ... end
|
|
98
|
-
def wait_for_deployment(name, namespace, timeout) ... end
|
|
99
|
-
def wait_for_statefulset(name, namespace, timeout) ... end
|
|
100
|
-
def get_pod_name(label) ... end
|
|
101
|
-
def exec(pod, command) ... end
|
|
102
|
-
end
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### 4. SSH as Dependency
|
|
106
|
-
|
|
107
|
-
All external adapters that run on remote servers need SSH:
|
|
108
|
-
|
|
109
|
-
- `Containerd.new(ssh)`
|
|
110
|
-
- `Kubectl.new(ssh)`
|
|
111
|
-
- `VolumeManager.new(ssh)` (if kept separate)
|
|
112
|
-
|
|
113
|
-
This is correct. SSH is the transport, others are domain-specific.
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## Migration Steps
|
|
118
|
-
|
|
119
|
-
1. Move `remote/ssh_executor.rb` → `external/ssh.rb`
|
|
120
|
-
2. Rename `remote/docker_manager.rb` → `external/containerd.rb`
|
|
121
|
-
3. Merge `remote/volume_manager.rb` into `external/containerd.rb` (or keep separate)
|
|
122
|
-
4. Extract kubectl operations from `k8s/renderer.rb` → `external/kubectl.rb`
|
|
123
|
-
5. Move template rendering to `utils/templates.rb` (already planned in 02-utils.md)
|
|
124
|
-
6. Update namespace from `Remote::SSHExecutor` to `External::SSH`
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
## Estimated Effort
|
|
129
|
-
|
|
130
|
-
- **Lines to consolidate:** ~426
|
|
131
|
-
- **Files created:** 3
|
|
132
|
-
- **Files deleted:** 4
|
|
133
|
-
- **DRY savings:** ~30 lines (simplified renderer, naming cleanup)
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
# 06 - CLI Entry Point (Thor Routing)
|
|
2
|
-
|
|
3
|
-
## Priority: FOURTH
|
|
4
|
-
|
|
5
|
-
CLI depends on all command implementations.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Current State
|
|
10
|
-
|
|
11
|
-
| File | Lines | Purpose |
|
|
12
|
-
| -------- | ----- | ----------------------------------------- |
|
|
13
|
-
| `cli.rb` | 191 | Thor commands + CredentialsCLI subcommand |
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Target Structure
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
lib/nvoi/
|
|
21
|
-
└── cli.rb # Thor routing only (~50 lines)
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## What CLI Currently Does
|
|
27
|
-
|
|
28
|
-
1. **Thor setup** - class options, exit behavior
|
|
29
|
-
2. **Command definitions** - `deploy`, `delete`, `exec`, `credentials`
|
|
30
|
-
3. **Config path resolution** - `resolve_config_path`, `resolve_working_dir`
|
|
31
|
-
4. **Instantiate services** - creates `DeployService`, `DeleteService`, etc.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## Target: Thin Router
|
|
36
|
-
|
|
37
|
-
CLI should ONLY:
|
|
38
|
-
|
|
39
|
-
1. Define Thor commands
|
|
40
|
-
2. Parse options
|
|
41
|
-
3. Delegate to `cli/<command>/command.rb`
|
|
42
|
-
|
|
43
|
-
```ruby
|
|
44
|
-
# cli.rb
|
|
45
|
-
module Nvoi
|
|
46
|
-
class CLI < Thor
|
|
47
|
-
class_option :config, aliases: "-c", default: "deploy.enc"
|
|
48
|
-
class_option :dir, aliases: "-d", default: "."
|
|
49
|
-
|
|
50
|
-
desc "deploy", "Deploy application"
|
|
51
|
-
option :dockerfile_path
|
|
52
|
-
def deploy
|
|
53
|
-
require_relative "cli/deploy/command"
|
|
54
|
-
CLI::Deploy::Command.new(options).run
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
desc "delete", "Delete infrastructure"
|
|
58
|
-
def delete
|
|
59
|
-
require_relative "cli/delete/command"
|
|
60
|
-
CLI::Delete::Command.new(options).run
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
desc "exec [COMMAND...]", "Execute command on server"
|
|
64
|
-
option :server, default: "main"
|
|
65
|
-
option :all, type: :boolean
|
|
66
|
-
option :interactive, aliases: "-i", type: :boolean
|
|
67
|
-
def exec(*args)
|
|
68
|
-
require_relative "cli/exec/command"
|
|
69
|
-
CLI::Exec::Command.new(options).run(args)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
desc "credentials SUBCOMMAND", "Manage credentials"
|
|
73
|
-
subcommand "credentials", CLI::Credentials
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
class CLI::Credentials < Thor
|
|
77
|
-
desc "edit", "Edit encrypted credentials"
|
|
78
|
-
def edit
|
|
79
|
-
require_relative "cli/credentials/edit/command"
|
|
80
|
-
CLI::Credentials::Edit::Command.new(options).run
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
desc "show", "Show decrypted credentials"
|
|
84
|
-
def show
|
|
85
|
-
require_relative "cli/credentials/show/command"
|
|
86
|
-
CLI::Credentials::Show::Command.new(options).run
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## DRY Opportunities
|
|
95
|
-
|
|
96
|
-
### 1. Move Logic to Commands
|
|
97
|
-
|
|
98
|
-
All the `resolve_config_path`, validation, service instantiation moves INTO each command.
|
|
99
|
-
|
|
100
|
-
### 2. Lazy Requires
|
|
101
|
-
|
|
102
|
-
Use `require_relative` inside methods. Faster CLI startup, only loads what's needed.
|
|
103
|
-
|
|
104
|
-
### 3. CredentialsCLI → Nested Class
|
|
105
|
-
|
|
106
|
-
Currently separate class, can be nested under CLI.
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## Migration Steps
|
|
111
|
-
|
|
112
|
-
1. Create command files first (07-10)
|
|
113
|
-
2. Strip `cli.rb` down to routing only
|
|
114
|
-
3. Move `CredentialsCLI` inline as `CLI::Credentials`
|
|
115
|
-
4. Add lazy requires for each command
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## Estimated Effort
|
|
120
|
-
|
|
121
|
-
- **Lines after refactor:** ~50 (down from 191)
|
|
122
|
-
- **Logic moved to:** `cli/*/command.rb` files
|
|
123
|
-
- **DRY savings:** 141 lines moved (not deleted, redistributed)
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
# 07 - CLI: Deploy Command
|
|
2
|
-
|
|
3
|
-
## Priority: FIFTH
|
|
4
|
-
|
|
5
|
-
Depends on external adapters, utils, objects.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Current State
|
|
10
|
-
|
|
11
|
-
| File | Lines | Purpose |
|
|
12
|
-
| ------------------- | ----- | --------------------------- |
|
|
13
|
-
| `service/deploy.rb` | 81 | DeployService orchestration |
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Target Structure
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
lib/nvoi/cli/deploy/
|
|
21
|
-
├── command.rb # Entry point + step orchestration
|
|
22
|
-
└── steps/
|
|
23
|
-
├── provision_network.rb
|
|
24
|
-
├── provision_server.rb
|
|
25
|
-
├── provision_volume.rb
|
|
26
|
-
├── setup_k3s.rb
|
|
27
|
-
├── configure_tunnel.rb
|
|
28
|
-
├── build_image.rb
|
|
29
|
-
├── deploy_database.rb
|
|
30
|
-
├── deploy_service.rb
|
|
31
|
-
└── cleanup_images.rb
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## What Deploy Does (Current Flow)
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
1. Load config
|
|
40
|
-
2. Init provider
|
|
41
|
-
3. Validate provider config
|
|
42
|
-
4. provision_server:
|
|
43
|
-
a. ServerProvisioner.run → provisions network, firewall, servers
|
|
44
|
-
b. VolumeProvisioner.run → creates/attaches/mounts volumes
|
|
45
|
-
c. K3sClusterSetup.run → installs K3s on master + workers
|
|
46
|
-
5. configure_tunnels:
|
|
47
|
-
a. TunnelConfigurator.run → creates Cloudflare tunnels + DNS
|
|
48
|
-
6. deploy_application:
|
|
49
|
-
a. Orchestrator.run:
|
|
50
|
-
- Acquire lock
|
|
51
|
-
- ImageBuilder.build_and_push
|
|
52
|
-
- Push to registry
|
|
53
|
-
- ServiceDeployer.deploy_app_secret
|
|
54
|
-
- ServiceDeployer.deploy_database
|
|
55
|
-
- ServiceDeployer.deploy_service (for each service)
|
|
56
|
-
- ServiceDeployer.deploy_app_service (for each app)
|
|
57
|
-
- ServiceDeployer.deploy_cloudflared
|
|
58
|
-
- ServiceDeployer.verify_traffic_switchover
|
|
59
|
-
- Cleaner.cleanup_old_images
|
|
60
|
-
- Release lock
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Target Flow (Simplified)
|
|
66
|
-
|
|
67
|
-
```ruby
|
|
68
|
-
# cli/deploy/command.rb
|
|
69
|
-
class Command
|
|
70
|
-
def run
|
|
71
|
-
config = Utils::ConfigLoader.new.load(config_path)
|
|
72
|
-
provider = External::Cloud.for(config)
|
|
73
|
-
log = Utils::Logger.new
|
|
74
|
-
|
|
75
|
-
Steps::ProvisionNetwork.new(config, provider, log).run
|
|
76
|
-
Steps::ProvisionServer.new(config, provider, log).run
|
|
77
|
-
Steps::ProvisionVolume.new(config, provider, log).run
|
|
78
|
-
Steps::SetupK3s.new(config, provider, log).run
|
|
79
|
-
tunnels = Steps::ConfigureTunnel.new(config, log).run
|
|
80
|
-
Steps::BuildImage.new(config, log).run
|
|
81
|
-
Steps::DeployService.new(config, tunnels, log).run
|
|
82
|
-
Steps::CleanupImages.new(config, log).run
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## DRY Opportunities
|
|
90
|
-
|
|
91
|
-
### 1. Flatten the Hierarchy
|
|
92
|
-
|
|
93
|
-
Current: `DeployService` → `Steps::*` → `Deployer::*`
|
|
94
|
-
Target: `Command` → `Steps::*` directly
|
|
95
|
-
|
|
96
|
-
Kill `Deployer::Orchestrator`. Its logic moves into `Steps::DeployService`.
|
|
97
|
-
|
|
98
|
-
### 2. Remove Thin Wrappers
|
|
99
|
-
|
|
100
|
-
`Steps::ApplicationDeployer` is just:
|
|
101
|
-
|
|
102
|
-
```ruby
|
|
103
|
-
def run
|
|
104
|
-
orchestrator = Deployer::Orchestrator.new(@config, @provider, @log)
|
|
105
|
-
orchestrator.run(@server_ip, @tunnels, @working_dir)
|
|
106
|
-
end
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Delete it. Put that logic in `Steps::DeployService`.
|
|
110
|
-
|
|
111
|
-
### 3. Consolidate ServiceDeployer
|
|
112
|
-
|
|
113
|
-
`Deployer::ServiceDeployer` (312 lines) does too much:
|
|
114
|
-
|
|
115
|
-
- Deploy app secret
|
|
116
|
-
- Deploy app service
|
|
117
|
-
- Deploy database
|
|
118
|
-
- Deploy generic service
|
|
119
|
-
- Deploy cloudflared
|
|
120
|
-
- Verify traffic
|
|
121
|
-
- Run pre-run commands
|
|
122
|
-
|
|
123
|
-
Split into focused steps or keep as internal helper in `Steps::DeployService`.
|
|
124
|
-
|
|
125
|
-
### 4. Lock Management
|
|
126
|
-
|
|
127
|
-
Deployment lock is in `Orchestrator`. Move to `Command`:
|
|
128
|
-
|
|
129
|
-
```ruby
|
|
130
|
-
def run
|
|
131
|
-
acquire_lock(ssh)
|
|
132
|
-
# ... steps ...
|
|
133
|
-
ensure
|
|
134
|
-
release_lock(ssh)
|
|
135
|
-
end
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## Migration Steps
|
|
141
|
-
|
|
142
|
-
1. Create `lib/nvoi/cli/deploy/command.rb`
|
|
143
|
-
2. Create `lib/nvoi/cli/deploy/steps/` directory
|
|
144
|
-
3. Move `steps/server_provisioner.rb` → `cli/deploy/steps/provision_server.rb`
|
|
145
|
-
4. Move `steps/volume_provisioner.rb` → `cli/deploy/steps/provision_volume.rb`
|
|
146
|
-
5. Merge `steps/k3s_cluster_setup.rb` + `steps/k3s_provisioner.rb` → `cli/deploy/steps/setup_k3s.rb`
|
|
147
|
-
6. Move `steps/tunnel_configurator.rb` → `cli/deploy/steps/configure_tunnel.rb`
|
|
148
|
-
7. Move `deployer/image_builder.rb` → `cli/deploy/steps/build_image.rb`
|
|
149
|
-
8. Merge `deployer/orchestrator.rb` + `deployer/service_deployer.rb` → `cli/deploy/steps/deploy_service.rb`
|
|
150
|
-
9. Move `deployer/cleaner.rb` → `cli/deploy/steps/cleanup_images.rb`
|
|
151
|
-
10. Extract network/firewall from ServerProvisioner → `cli/deploy/steps/provision_network.rb`
|
|
152
|
-
11. Delete old files: `service/deploy.rb`, `deployer/orchestrator.rb`, `steps/application_deployer.rb`
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## Estimated Effort
|
|
157
|
-
|
|
158
|
-
**Files involved:**
|
|
159
|
-
|
|
160
|
-
- `service/deploy.rb` (81 lines)
|
|
161
|
-
- `steps/server_provisioner.rb` (44 lines)
|
|
162
|
-
- `steps/volume_provisioner.rb` (155 lines)
|
|
163
|
-
- `steps/k3s_provisioner.rb` (352 lines)
|
|
164
|
-
- `steps/k3s_cluster_setup.rb` (106 lines)
|
|
165
|
-
- `steps/tunnel_configurator.rb` (67 lines)
|
|
166
|
-
- `steps/application_deployer.rb` (27 lines)
|
|
167
|
-
- `deployer/orchestrator.rb` (147 lines)
|
|
168
|
-
- `deployer/infrastructure.rb` (127 lines)
|
|
169
|
-
- `deployer/image_builder.rb` (24 lines)
|
|
170
|
-
- `deployer/service_deployer.rb` (312 lines)
|
|
171
|
-
- `deployer/cleaner.rb` (37 lines)
|
|
172
|
-
|
|
173
|
-
**Total: ~1479 lines to reorganize**
|
|
174
|
-
|
|
175
|
-
- **Files created:** 9 (command.rb + 8 steps)
|
|
176
|
-
- **Files deleted:** 12
|
|
177
|
-
- **DRY savings:** ~150 lines (removed wrappers, flattened hierarchy)
|