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,201 +0,0 @@
|
|
|
1
|
-
# 08 - CLI: Deploy Steps (Details)
|
|
2
|
-
|
|
3
|
-
## Priority: FIFTH (part of deploy)
|
|
4
|
-
|
|
5
|
-
Each step is a focused, single-responsibility class.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Step 1: provision_network.rb (~50 lines)
|
|
10
|
-
|
|
11
|
-
**Source:** Extract from `deployer/infrastructure.rb`
|
|
12
|
-
|
|
13
|
-
**Does:**
|
|
14
|
-
|
|
15
|
-
- Find or create network
|
|
16
|
-
- Find or create firewall
|
|
17
|
-
|
|
18
|
-
```ruby
|
|
19
|
-
# cli/deploy/steps/provision_network.rb
|
|
20
|
-
module Nvoi
|
|
21
|
-
module CLI
|
|
22
|
-
module Deploy
|
|
23
|
-
module Steps
|
|
24
|
-
class ProvisionNetwork
|
|
25
|
-
def initialize(config, provider, log)
|
|
26
|
-
@config = config
|
|
27
|
-
@provider = provider
|
|
28
|
-
@log = log
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def run
|
|
32
|
-
@log.info "Provisioning network: %s", @config.network_name
|
|
33
|
-
network = @provider.find_or_create_network(@config.network_name)
|
|
34
|
-
|
|
35
|
-
@log.info "Provisioning firewall: %s", @config.firewall_name
|
|
36
|
-
firewall = @provider.find_or_create_firewall(@config.firewall_name)
|
|
37
|
-
|
|
38
|
-
{ network: network, firewall: firewall }
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## Step 2: provision_server.rb (~80 lines)
|
|
50
|
-
|
|
51
|
-
**Source:** `steps/server_provisioner.rb` + `deployer/infrastructure.rb`
|
|
52
|
-
|
|
53
|
-
**Does:**
|
|
54
|
-
|
|
55
|
-
- Create servers for each group
|
|
56
|
-
- Wait for SSH ready
|
|
57
|
-
- Return main server IP
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## Step 3: provision_volume.rb (~150 lines)
|
|
62
|
-
|
|
63
|
-
**Source:** `steps/volume_provisioner.rb`
|
|
64
|
-
|
|
65
|
-
**Does:**
|
|
66
|
-
|
|
67
|
-
- Collect all volumes needed (database, services, app)
|
|
68
|
-
- Create volumes via provider
|
|
69
|
-
- Attach to servers
|
|
70
|
-
- Mount via SSH (format if needed, add to fstab)
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## Step 4: setup_k3s.rb (~400 lines)
|
|
75
|
-
|
|
76
|
-
**Source:** Merge `steps/k3s_provisioner.rb` + `steps/k3s_cluster_setup.rb`
|
|
77
|
-
|
|
78
|
-
**Does:**
|
|
79
|
-
|
|
80
|
-
- Wait for cloud-init
|
|
81
|
-
- Install K3s server on master
|
|
82
|
-
- Setup kubeconfig
|
|
83
|
-
- Setup in-cluster registry
|
|
84
|
-
- Setup NGINX ingress
|
|
85
|
-
- Install K3s agent on workers
|
|
86
|
-
- Label nodes
|
|
87
|
-
- Get cluster token
|
|
88
|
-
|
|
89
|
-
**Note:** This is the largest step. Consider keeping as single file with private methods, or split into:
|
|
90
|
-
|
|
91
|
-
- `setup_k3s/master.rb`
|
|
92
|
-
- `setup_k3s/worker.rb`
|
|
93
|
-
- `setup_k3s/registry.rb`
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## Step 5: configure_tunnel.rb (~70 lines)
|
|
98
|
-
|
|
99
|
-
**Source:** `steps/tunnel_configurator.rb`
|
|
100
|
-
|
|
101
|
-
**Does:**
|
|
102
|
-
|
|
103
|
-
- For each app service with domain:
|
|
104
|
-
- Create/find Cloudflare tunnel
|
|
105
|
-
- Configure tunnel ingress
|
|
106
|
-
- Create DNS CNAME record
|
|
107
|
-
- Return array of TunnelInfo
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
|
-
## Step 6: build_image.rb (~100 lines)
|
|
112
|
-
|
|
113
|
-
**Source:** `deployer/image_builder.rb` + parts of `remote/docker_manager.rb`
|
|
114
|
-
|
|
115
|
-
**Does:**
|
|
116
|
-
|
|
117
|
-
- Build Docker image locally
|
|
118
|
-
- Save to tar
|
|
119
|
-
- Rsync to remote server
|
|
120
|
-
- Import into containerd
|
|
121
|
-
- Tag image
|
|
122
|
-
- Push to in-cluster registry
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## Step 7: deploy_database.rb (~80 lines)
|
|
127
|
-
|
|
128
|
-
**Source:** `steps/database_provisioner.rb` + `deployer/service_deployer.rb#deploy_database`
|
|
129
|
-
|
|
130
|
-
**Does:**
|
|
131
|
-
|
|
132
|
-
- Skip if SQLite (handled by app volumes)
|
|
133
|
-
- Deploy database secret (POSTGRES_USER, etc.)
|
|
134
|
-
- Deploy StatefulSet with hostPath volume
|
|
135
|
-
- Wait for database pod to be Running
|
|
136
|
-
|
|
137
|
-
---
|
|
138
|
-
|
|
139
|
-
## Step 8: deploy_service.rb (~300 lines)
|
|
140
|
-
|
|
141
|
-
**Source:** Merge `deployer/orchestrator.rb` + `deployer/service_deployer.rb`
|
|
142
|
-
|
|
143
|
-
**Does:**
|
|
144
|
-
|
|
145
|
-
- Deploy app secret (env vars)
|
|
146
|
-
- Deploy additional services (redis, etc.)
|
|
147
|
-
- Deploy app services (web, worker)
|
|
148
|
-
- Deploy cloudflared sidecars
|
|
149
|
-
- Verify traffic routing
|
|
150
|
-
- Run pre-run commands (migrations)
|
|
151
|
-
|
|
152
|
-
**Note:** Uses `External::Kubectl` for all manifest operations.
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## Step 9: cleanup_images.rb (~40 lines)
|
|
157
|
-
|
|
158
|
-
**Source:** `deployer/cleaner.rb`
|
|
159
|
-
|
|
160
|
-
**Does:**
|
|
161
|
-
|
|
162
|
-
- List all images with prefix
|
|
163
|
-
- Keep newest N images
|
|
164
|
-
- Delete the rest
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## Step Dependencies
|
|
169
|
-
|
|
170
|
-
```
|
|
171
|
-
provision_network ─┐
|
|
172
|
-
├─► provision_server ─► provision_volume ─► setup_k3s
|
|
173
|
-
│
|
|
174
|
-
configure_tunnel ──┼─► build_image ─► deploy_service ─► cleanup_images
|
|
175
|
-
│
|
|
176
|
-
└── (parallel where possible)
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
---
|
|
180
|
-
|
|
181
|
-
## Common Patterns Across Steps
|
|
182
|
-
|
|
183
|
-
### Constructor Signature
|
|
184
|
-
|
|
185
|
-
All steps follow:
|
|
186
|
-
|
|
187
|
-
```ruby
|
|
188
|
-
def initialize(config, provider_or_deps, log)
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### Return Values
|
|
192
|
-
|
|
193
|
-
- Most return `nil` (side effects only)
|
|
194
|
-
- Some return data for next step:
|
|
195
|
-
- `provision_server` → main_server_ip
|
|
196
|
-
- `configure_tunnel` → [TunnelInfo]
|
|
197
|
-
- `provision_network` → { network:, firewall: }
|
|
198
|
-
|
|
199
|
-
### Error Handling
|
|
200
|
-
|
|
201
|
-
Steps raise specific errors. Command catches and logs.
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
# 09 - CLI: Delete Command
|
|
2
|
-
|
|
3
|
-
## Priority: FIFTH (parallel with deploy)
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Current State
|
|
8
|
-
|
|
9
|
-
| File | Lines | Purpose |
|
|
10
|
-
| ------------------- | ----- | -------------------------------------- |
|
|
11
|
-
| `service/delete.rb` | 235 | DeleteService - teardown all resources |
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Target Structure
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
lib/nvoi/cli/delete/
|
|
19
|
-
├── command.rb
|
|
20
|
-
└── steps/
|
|
21
|
-
├── teardown_tunnel.rb
|
|
22
|
-
├── teardown_dns.rb
|
|
23
|
-
├── detach_volumes.rb
|
|
24
|
-
├── teardown_server.rb
|
|
25
|
-
├── teardown_volume.rb
|
|
26
|
-
├── teardown_firewall.rb
|
|
27
|
-
└── teardown_network.rb
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## What Delete Does (Current)
|
|
33
|
-
|
|
34
|
-
```ruby
|
|
35
|
-
def run
|
|
36
|
-
detach_volumes # Must happen before server deletion
|
|
37
|
-
delete_all_servers # Delete servers from all groups
|
|
38
|
-
delete_volumes # Now safe to delete
|
|
39
|
-
delete_firewall # With retry (might still be attached)
|
|
40
|
-
delete_network
|
|
41
|
-
delete_cloudflare_resources # Tunnels + DNS records
|
|
42
|
-
end
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Target Flow
|
|
48
|
-
|
|
49
|
-
```ruby
|
|
50
|
-
# cli/delete/command.rb
|
|
51
|
-
class Command
|
|
52
|
-
def run
|
|
53
|
-
config = Utils::ConfigLoader.new.load(config_path)
|
|
54
|
-
provider = External::Cloud.for(config)
|
|
55
|
-
cloudflare = External::DNS::Cloudflare.new(config.cloudflare)
|
|
56
|
-
log = Utils::Logger.new
|
|
57
|
-
|
|
58
|
-
# Order matters!
|
|
59
|
-
Steps::DetachVolumes.new(config, provider, log).run
|
|
60
|
-
Steps::TeardownServer.new(config, provider, log).run
|
|
61
|
-
Steps::TeardownVolume.new(config, provider, log).run
|
|
62
|
-
Steps::TeardownFirewall.new(config, provider, log).run
|
|
63
|
-
Steps::TeardownNetwork.new(config, provider, log).run
|
|
64
|
-
Steps::TeardownTunnel.new(config, cloudflare, log).run
|
|
65
|
-
Steps::TeardownDNS.new(config, cloudflare, log).run
|
|
66
|
-
|
|
67
|
-
log.success "Cleanup complete"
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## DRY Opportunities
|
|
75
|
-
|
|
76
|
-
### 1. Shared Volume Name Collection
|
|
77
|
-
|
|
78
|
-
Both `detach_volumes` and `delete_volumes` call `collect_volume_names`.
|
|
79
|
-
Extract to utils or pass as step output:
|
|
80
|
-
|
|
81
|
-
```ruby
|
|
82
|
-
volumes = Steps::DetachVolumes.new(...).run # returns volume list
|
|
83
|
-
Steps::TeardownVolume.new(...).run(volumes)
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### 2. Shared Hostname Builder
|
|
87
|
-
|
|
88
|
-
`build_hostname` is duplicated. Already planned for `utils/namer.rb`.
|
|
89
|
-
|
|
90
|
-
### 3. Merge Tunnel + DNS Teardown
|
|
91
|
-
|
|
92
|
-
Both iterate over app services with domains. Could be single step:
|
|
93
|
-
|
|
94
|
-
```ruby
|
|
95
|
-
Steps::TeardownCloudflare.new(config, cloudflare, log).run
|
|
96
|
-
# Deletes both tunnels and DNS records
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### 4. Error Tolerance
|
|
100
|
-
|
|
101
|
-
Delete operations should be idempotent. Current code does:
|
|
102
|
-
|
|
103
|
-
```ruby
|
|
104
|
-
rescue StandardError => e
|
|
105
|
-
@log.warning "Failed to delete: %s", e.message
|
|
106
|
-
end
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Keep this pattern - deletion should continue even if some resources are already gone.
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## Step Details
|
|
114
|
-
|
|
115
|
-
### detach_volumes.rb (~40 lines)
|
|
116
|
-
|
|
117
|
-
- Collect all volume names
|
|
118
|
-
- For each: find volume, detach if attached
|
|
119
|
-
|
|
120
|
-
### teardown_server.rb (~50 lines)
|
|
121
|
-
|
|
122
|
-
- For each server group:
|
|
123
|
-
- For each server in group:
|
|
124
|
-
- Find and delete server
|
|
125
|
-
|
|
126
|
-
### teardown_volume.rb (~40 lines)
|
|
127
|
-
|
|
128
|
-
- For each volume name:
|
|
129
|
-
- Find and delete volume
|
|
130
|
-
|
|
131
|
-
### teardown_firewall.rb (~30 lines)
|
|
132
|
-
|
|
133
|
-
- Find firewall by name
|
|
134
|
-
- Delete with retry (may still be detaching)
|
|
135
|
-
|
|
136
|
-
### teardown_network.rb (~20 lines)
|
|
137
|
-
|
|
138
|
-
- Find network by name
|
|
139
|
-
- Delete
|
|
140
|
-
|
|
141
|
-
### teardown_tunnel.rb (~40 lines)
|
|
142
|
-
|
|
143
|
-
- For each app service with domain:
|
|
144
|
-
- Find and delete tunnel
|
|
145
|
-
|
|
146
|
-
### teardown_dns.rb (~40 lines)
|
|
147
|
-
|
|
148
|
-
- For each app service with domain:
|
|
149
|
-
- Find zone
|
|
150
|
-
- Find and delete CNAME record
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## Migration Steps
|
|
155
|
-
|
|
156
|
-
1. Create `lib/nvoi/cli/delete/command.rb`
|
|
157
|
-
2. Create `lib/nvoi/cli/delete/steps/` directory
|
|
158
|
-
3. Extract each operation from `service/delete.rb` into separate step
|
|
159
|
-
4. Move `collect_volume_names` to command (shared state)
|
|
160
|
-
5. Delete `service/delete.rb`
|
|
161
|
-
|
|
162
|
-
---
|
|
163
|
-
|
|
164
|
-
## Estimated Effort
|
|
165
|
-
|
|
166
|
-
- **Lines to reorganize:** 235
|
|
167
|
-
- **Files created:** 8 (command + 7 steps)
|
|
168
|
-
- **Files deleted:** 1
|
|
169
|
-
- **DRY savings:** ~30 lines (shared hostname, merged tunnel+dns option)
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# 10 - CLI: Exec Command
|
|
2
|
-
|
|
3
|
-
## Priority: FIFTH (parallel with deploy/delete)
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Current State
|
|
8
|
-
|
|
9
|
-
| File | Lines | Purpose |
|
|
10
|
-
|------|-------|---------|
|
|
11
|
-
| `service/exec.rb` | 145 | ExecService - remote command execution |
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Target Structure
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
lib/nvoi/cli/exec/
|
|
19
|
-
└── command.rb # Single file, no steps needed
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## What Exec Does
|
|
25
|
-
|
|
26
|
-
Three modes:
|
|
27
|
-
1. **Single server:** `nvoi exec "ls -la"` → run on main server
|
|
28
|
-
2. **All servers:** `nvoi exec --all "ls -la"` → run on all servers in parallel
|
|
29
|
-
3. **Interactive:** `nvoi exec -i` → open SSH shell
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Target Implementation
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
# cli/exec/command.rb
|
|
37
|
-
module Nvoi
|
|
38
|
-
module CLI
|
|
39
|
-
module Exec
|
|
40
|
-
class Command
|
|
41
|
-
def initialize(options)
|
|
42
|
-
@options = options
|
|
43
|
-
@log = Utils::Logger.new
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def run(args)
|
|
47
|
-
config = Utils::ConfigLoader.new.load(config_path)
|
|
48
|
-
provider = External::Cloud.for(config)
|
|
49
|
-
|
|
50
|
-
if @options[:interactive]
|
|
51
|
-
open_shell(config, provider)
|
|
52
|
-
elsif @options[:all]
|
|
53
|
-
run_all(config, provider, args.join(" "))
|
|
54
|
-
else
|
|
55
|
-
run_single(config, provider, args.join(" "), @options[:server])
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
private
|
|
60
|
-
|
|
61
|
-
def run_single(config, provider, command, server_name)
|
|
62
|
-
server = find_server(config, provider, server_name)
|
|
63
|
-
ssh = External::SSH.new(server.public_ipv4, config.ssh_key_path)
|
|
64
|
-
|
|
65
|
-
@log.info "Executing on %s: %s", server.name, command
|
|
66
|
-
ssh.execute(command, stream: true)
|
|
67
|
-
@log.success "Command completed"
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def run_all(config, provider, command)
|
|
71
|
-
servers = all_servers(config, provider)
|
|
72
|
-
@log.info "Executing on %d servers", servers.size
|
|
73
|
-
|
|
74
|
-
threads = servers.map do |server|
|
|
75
|
-
Thread.new do
|
|
76
|
-
ssh = External::SSH.new(server.public_ipv4, config.ssh_key_path)
|
|
77
|
-
output = ssh.execute(command)
|
|
78
|
-
[server.name, output]
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
threads.each do |t|
|
|
83
|
-
name, output = t.value
|
|
84
|
-
output.lines.each { |line| puts "[#{name}] #{line}" }
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def open_shell(config, provider)
|
|
89
|
-
server = find_server(config, provider, @options[:server])
|
|
90
|
-
ssh = External::SSH.new(server.public_ipv4, config.ssh_key_path)
|
|
91
|
-
ssh.open_shell
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def find_server(config, provider, name)
|
|
95
|
-
actual_name = resolve_server_name(config, name)
|
|
96
|
-
provider.find_server(actual_name) or raise ServiceError, "server not found: #{actual_name}"
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def resolve_server_name(config, name)
|
|
100
|
-
return config.server_name if name == "main" || name.nil?
|
|
101
|
-
# Parse "worker-1" → server_name("worker", 1)
|
|
102
|
-
# ...
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def all_servers(config, provider)
|
|
106
|
-
config.deploy.application.servers.flat_map do |group, cfg|
|
|
107
|
-
(1..cfg.count).map { |i| provider.find_server(config.namer.server_name(group, i)) }
|
|
108
|
-
end.compact
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## DRY Opportunities
|
|
119
|
-
|
|
120
|
-
### 1. Server Name Resolution
|
|
121
|
-
`resolve_server_name` logic is useful. Could go to `utils/namer.rb`:
|
|
122
|
-
```ruby
|
|
123
|
-
# utils/namer.rb
|
|
124
|
-
def resolve_server_reference(ref)
|
|
125
|
-
# "main" → server_name for master
|
|
126
|
-
# "worker-1" → server_name("worker", 1)
|
|
127
|
-
end
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### 2. Server Collection
|
|
131
|
-
`all_servers` pattern could be a config method:
|
|
132
|
-
```ruby
|
|
133
|
-
# objects/config.rb
|
|
134
|
-
def each_server
|
|
135
|
-
deploy.application.servers.each do |group, cfg|
|
|
136
|
-
(1..cfg.count).each { |i| yield(group, i) }
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## Migration Steps
|
|
144
|
-
|
|
145
|
-
1. Create `lib/nvoi/cli/exec/command.rb`
|
|
146
|
-
2. Move logic from `service/exec.rb`
|
|
147
|
-
3. Extract `resolve_server_name` to `utils/namer.rb`
|
|
148
|
-
4. Delete `service/exec.rb`
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## Estimated Effort
|
|
153
|
-
|
|
154
|
-
- **Lines to reorganize:** 145
|
|
155
|
-
- **Files created:** 1
|
|
156
|
-
- **Files deleted:** 1
|
|
157
|
-
- **Net change:** ~0 (simple move + namespace change)
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
# 11 - CLI: Credentials Commands
|
|
2
|
-
|
|
3
|
-
## Priority: FIFTH (parallel with others)
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Current State
|
|
8
|
-
|
|
9
|
-
| File | Lines | Purpose |
|
|
10
|
-
| ------------------------- | ----- | ------------------------------- |
|
|
11
|
-
| `cli.rb` (CredentialsCLI) | ~55 | Thor subcommand for credentials |
|
|
12
|
-
| `credentials/editor.rb` | ~80 | Edit/show encrypted credentials |
|
|
13
|
-
| `credentials/manager.rb` | ~150 | Load/save encrypted files |
|
|
14
|
-
| `credentials/crypto.rb` | ~100 | AES encryption |
|
|
15
|
-
|
|
16
|
-
**Total: ~385 lines**
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Target Structure
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
lib/nvoi/cli/credentials/
|
|
24
|
-
├── edit/
|
|
25
|
-
│ └── command.rb # nvoi credentials edit
|
|
26
|
-
└── show/
|
|
27
|
-
└── command.rb # nvoi credentials show
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Note: `crypto.rb` and `manager.rb` move to `utils/` (see 02-utils.md)
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## What Each Command Does
|
|
35
|
-
|
|
36
|
-
### credentials edit
|
|
37
|
-
|
|
38
|
-
1. Load or create credentials file
|
|
39
|
-
2. Decrypt to temp file
|
|
40
|
-
3. Open in $EDITOR
|
|
41
|
-
4. Validate YAML on save
|
|
42
|
-
5. Re-encrypt
|
|
43
|
-
6. Update .gitignore if first run
|
|
44
|
-
|
|
45
|
-
### credentials show
|
|
46
|
-
|
|
47
|
-
1. Load credentials file
|
|
48
|
-
2. Decrypt
|
|
49
|
-
3. Print to stdout
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## Target Implementation
|
|
54
|
-
|
|
55
|
-
```ruby
|
|
56
|
-
# cli/credentials/edit/command.rb
|
|
57
|
-
module Nvoi
|
|
58
|
-
module CLI
|
|
59
|
-
module Credentials
|
|
60
|
-
module Edit
|
|
61
|
-
class Command
|
|
62
|
-
def initialize(options)
|
|
63
|
-
@options = options
|
|
64
|
-
@log = Utils::Logger.new
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def run
|
|
68
|
-
working_dir = resolve_working_dir
|
|
69
|
-
store = Utils::CredentialStore.new(working_dir, @options[:credentials], @options[:master_key])
|
|
70
|
-
|
|
71
|
-
if store.exists?
|
|
72
|
-
edit_existing(store)
|
|
73
|
-
else
|
|
74
|
-
create_new(store)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
private
|
|
79
|
-
|
|
80
|
-
def edit_existing(store)
|
|
81
|
-
content = store.read
|
|
82
|
-
new_content = open_in_editor(content)
|
|
83
|
-
validate_yaml!(new_content)
|
|
84
|
-
store.write(new_content)
|
|
85
|
-
@log.success "Credentials updated"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def create_new(store)
|
|
89
|
-
@log.info "Creating new credentials file"
|
|
90
|
-
template = default_template
|
|
91
|
-
new_content = open_in_editor(template)
|
|
92
|
-
validate_yaml!(new_content)
|
|
93
|
-
store.write(new_content)
|
|
94
|
-
store.update_gitignore
|
|
95
|
-
@log.success "Credentials created"
|
|
96
|
-
@log.warning "Keep your master key safe!"
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def open_in_editor(content)
|
|
100
|
-
# Write to temp file, exec $EDITOR, read back
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def validate_yaml!(content)
|
|
104
|
-
YAML.safe_load(content)
|
|
105
|
-
rescue Psych::SyntaxError => e
|
|
106
|
-
raise ConfigError, "Invalid YAML: #{e.message}"
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
```ruby
|
|
116
|
-
# cli/credentials/show/command.rb
|
|
117
|
-
module Nvoi
|
|
118
|
-
module CLI
|
|
119
|
-
module Credentials
|
|
120
|
-
module Show
|
|
121
|
-
class Command
|
|
122
|
-
def initialize(options)
|
|
123
|
-
@options = options
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def run
|
|
127
|
-
working_dir = resolve_working_dir
|
|
128
|
-
store = Utils::CredentialStore.new(working_dir, @options[:credentials], @options[:master_key])
|
|
129
|
-
puts store.read
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## DRY Opportunities
|
|
141
|
-
|
|
142
|
-
### 1. Merge Crypto + Manager → CredentialStore
|
|
143
|
-
|
|
144
|
-
Already planned in 02-utils.md. Single class:
|
|
145
|
-
|
|
146
|
-
```ruby
|
|
147
|
-
# utils/crypto.rb
|
|
148
|
-
class CredentialStore
|
|
149
|
-
def initialize(working_dir, enc_path = nil, key_path = nil)
|
|
150
|
-
def exists? → bool
|
|
151
|
-
def read → String
|
|
152
|
-
def write(content)
|
|
153
|
-
def update_gitignore
|
|
154
|
-
end
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### 2. Remove Editor Class
|
|
158
|
-
|
|
159
|
-
Current `credentials/editor.rb` is thin wrapper. Logic goes directly into command.
|
|
160
|
-
|
|
161
|
-
### 3. YAML Validation
|
|
162
|
-
|
|
163
|
-
Move to utils for reuse:
|
|
164
|
-
|
|
165
|
-
```ruby
|
|
166
|
-
# utils/config_loader.rb
|
|
167
|
-
def self.validate_yaml!(content)
|
|
168
|
-
YAML.safe_load(content, permitted_classes: [Symbol])
|
|
169
|
-
end
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## Migration Steps
|
|
175
|
-
|
|
176
|
-
1. Move `credentials/crypto.rb` + `credentials/manager.rb` → `utils/crypto.rb` (02-utils.md)
|
|
177
|
-
2. Create `lib/nvoi/cli/credentials/edit/command.rb`
|
|
178
|
-
3. Create `lib/nvoi/cli/credentials/show/command.rb`
|
|
179
|
-
4. Move editor logic into `edit/command.rb`
|
|
180
|
-
5. Delete `credentials/editor.rb`
|
|
181
|
-
6. Update `cli.rb` to route to new commands
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
## Estimated Effort
|
|
186
|
-
|
|
187
|
-
- **Lines to reorganize:** ~385
|
|
188
|
-
- **Files created:** 2
|
|
189
|
-
- **Files deleted:** 3 (`editor.rb`, `manager.rb`, `crypto.rb` → merged to utils)
|
|
190
|
-
- **Net reduction:** ~50 lines (removed wrapper, merged classes)
|