odysseus-core 0.10.0 → 0.10.1
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/CHANGELOG.md +72 -0
- data/lib/odysseus/builder/client.rb +85 -6
- data/lib/odysseus/core/version.rb +1 -1
- data/lib/odysseus/deployer/ssh.rb +24 -2
- data/lib/odysseus/docker/client.rb +31 -0
- data/lib/odysseus/ssh_key.rb +68 -0
- data/lib/odysseus.rb +1 -0
- metadata +30 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cfc5413226161f5c572dfac62bc9f3a1c13374135525e023d7462b9531bea3b7
|
|
4
|
+
data.tar.gz: 6dcd045639190f23f0131a1b5042c5c3fc378521dcbe7151305a2983fbbb38f5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a9fc554c9aa8188282f3a5d3cf1869b8df86c8145c1895ba4f837d38e896d6f90509922e31eb83150fd451f09862a1a39ef43927f48d6e1cc2ecb3cd14cd795c
|
|
7
|
+
data.tar.gz: 5968a74f44b8184f1c1cd53661073312cf7ca081a4c9f8eef68637624465a99ffc1f3bea626a8fedaba17a0b72a6231ed91142e11345ab034594c5bf2c76bf5e
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,59 @@ gem artifacts, so they are summaries rather than contemporaneous notes.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.10.1] - 2026-09-16
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- odysseus could not read an SSH key from disk unless ssh-agent happened to be
|
|
15
|
+
holding it. `net-ssh` needs `ed25519` and `bcrypt_pbkdf` to parse anything in
|
|
16
|
+
the `-----BEGIN OPENSSH PRIVATE KEY-----` container -- which is what every
|
|
17
|
+
modern `ssh-keygen` writes, **RSA included, not only ed25519** -- and neither
|
|
18
|
+
was declared, so `key_factory.rb` raised `NotImplementedError` before it ever
|
|
19
|
+
looked at the algorithm. Both are runtime dependencies now. Reported by a
|
|
20
|
+
tester whose deploys failed with a passphrase-protected key; the passphrase
|
|
21
|
+
was not the cause, the key format was.
|
|
22
|
+
|
|
23
|
+
- A passphrase-protected SSH key is no longer reported as "Check your SSH
|
|
24
|
+
keys." odysseus connects with `non_interactive: true` -- a passphrase prompt
|
|
25
|
+
on a deploy connection can never be answered, so prompting would hang rather
|
|
26
|
+
than help -- and net-ssh reports the resulting key-loading failure to a
|
|
27
|
+
logger rather than raising it, so with none configured the reason was
|
|
28
|
+
discarded and the reader was sent to inspect keys that were entirely correct.
|
|
29
|
+
The failure now names each locked key and gives the `ssh-add` line for it.
|
|
30
|
+
`Odysseus::SSHKey.encrypted?` answers that by reading the cipher named in the
|
|
31
|
+
key file, so no passphrase is needed to detect one.
|
|
32
|
+
|
|
33
|
+
- `docker pussh` no longer hangs silently. It ran through backticks with
|
|
34
|
+
everything folded into a captured string, which had three consequences: no
|
|
35
|
+
output reached the terminal until the push finished, so a healthy push and a
|
|
36
|
+
stuck one looked identical; there was no timeout; and stdin stayed attached
|
|
37
|
+
to the parent, so a child that asked a question waited forever on an answer
|
|
38
|
+
nobody could see it requesting. Output now streams as it arrives, stdin is
|
|
39
|
+
closed so any prompt reaches EOF and fails promptly, and a push is bounded by
|
|
40
|
+
`PUSSH_TIMEOUT` (30 minutes -- generous, since it exists to end a hang rather
|
|
41
|
+
than to police a slow link). A push that does time out now suggests what to
|
|
42
|
+
check, including the Tailscale steps `SSH#with_connection` already gives for
|
|
43
|
+
the deploy connection but which `pussh` never reached.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- Every container odysseus starts now has a bounded log. Containers inherited
|
|
48
|
+
the daemon's default logging, which on a stock Docker install is `json-file`
|
|
49
|
+
with no size limit, so a long-lived container filled the disk -- most visibly
|
|
50
|
+
`odysseus-caddy`, the one container no deploy ever replaces. Every
|
|
51
|
+
`docker run` now carries `--log-driver json-file --log-opt max-size=100m
|
|
52
|
+
--log-opt max-file=3`.
|
|
53
|
+
|
|
54
|
+
The driver is named rather than left to the daemon because `max-size` and
|
|
55
|
+
`max-file` belong to `json-file` and `local` only: on a host defaulting to
|
|
56
|
+
`journald` or `syslog`, docker refuses the run rather than ignoring them.
|
|
57
|
+
Naming it also makes explicit what `odysseus logs` already assumed, since it
|
|
58
|
+
shells out to `docker logs`.
|
|
59
|
+
|
|
60
|
+
**This overrides a deliberate daemon-wide log driver on the host.** Making the
|
|
61
|
+
driver and options configurable per service is tracked in TODO.md.
|
|
62
|
+
|
|
10
63
|
## [0.10.0] - 2026-08-28
|
|
11
64
|
|
|
12
65
|
### Fixed
|
|
@@ -471,6 +524,25 @@ previously reported success while failing will now stop and say so.
|
|
|
471
524
|
- `rake` runs RSpec and RuboCop clean. Remaining Metrics offences are recorded
|
|
472
525
|
in `.rubocop_todo.yml` rather than hidden in the main configuration.
|
|
473
526
|
|
|
527
|
+
## [0.3.2] - 2026-08-12
|
|
528
|
+
|
|
529
|
+
Reconstructed after the fact: 0.3.2 was bumped in `version.rb` but never
|
|
530
|
+
tagged, and the change first reached users in 0.4.0.
|
|
531
|
+
|
|
532
|
+
### Fixed
|
|
533
|
+
|
|
534
|
+
- Adding a hostname to `proxy.hosts` had no effect on an already-deployed
|
|
535
|
+
service. `Caddy::Client#add_upstream` reconciled only the upstreams of an
|
|
536
|
+
existing route, never its Host matcher, so the route kept whatever hosts it
|
|
537
|
+
was created with until it was deleted by hand. TLS was unaffected --
|
|
538
|
+
`enable_tls_for_hosts` always received the full list -- which made the
|
|
539
|
+
failure confusing: Caddy answered the new hostname over HTTPS with a valid
|
|
540
|
+
certificate, matched no route, and served the default handler instead of the
|
|
541
|
+
service. `add_upstream` now PATCHes `match/0/host` when the configured hosts
|
|
542
|
+
differ from the deployed ones. The repair needs one deploy: the reconcile
|
|
543
|
+
runs during `add_upstream`, so upgrading alone leaves an existing route as it
|
|
544
|
+
was.
|
|
545
|
+
|
|
474
546
|
## [0.3.1] - 2026-04-05
|
|
475
547
|
|
|
476
548
|
### Fixed
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# lib/odysseus/builder/client.rb
|
|
2
2
|
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
3
5
|
module Odysseus
|
|
4
6
|
module Builder
|
|
5
7
|
class Client
|
|
8
|
+
# A push moves a whole image over SSH, so this is generous -- it exists
|
|
9
|
+
# to end a hang, not to police a slow link.
|
|
10
|
+
PUSSH_TIMEOUT = 1800 # seconds
|
|
11
|
+
|
|
6
12
|
# @param config [Hash] builder config from deploy.yml
|
|
7
13
|
# @param ssh_config [Hash] SSH config (user, keys)
|
|
8
14
|
# @param logger [Object] logger
|
|
@@ -85,11 +91,28 @@ module Odysseus
|
|
|
85
91
|
cmd = "docker pussh #{image} #{target}"
|
|
86
92
|
@logger.debug(cmd) if @logger.respond_to?(:debug)
|
|
87
93
|
|
|
88
|
-
output = execute_local_command(cmd)
|
|
94
|
+
output = execute_local_command(cmd, timeout: PUSSH_TIMEOUT)
|
|
89
95
|
{ success: true, output: output, host: host }
|
|
90
96
|
rescue BuildError => e
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
message = push_failure_message(e, host)
|
|
98
|
+
@logger.error("Pussh failed: #{message}")
|
|
99
|
+
{ success: false, error: message, host: host }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# A push that timed out almost never failed because of docker. It failed
|
|
103
|
+
# because the host could not be reached, and the commonest reason here is
|
|
104
|
+
# Tailscale -- which SSH#with_connection already explains for the deploy
|
|
105
|
+
# connection, but `docker pussh` shells out to the system ssh and reaches
|
|
106
|
+
# none of that handling.
|
|
107
|
+
def push_failure_message(error, host)
|
|
108
|
+
return error.message unless error.message.include?('timed out')
|
|
109
|
+
|
|
110
|
+
"#{error.message}\n\n" \
|
|
111
|
+
"The push could not reach #{host}. Worth checking:\n " \
|
|
112
|
+
"1. Tailscale is running: tailscale status\n " \
|
|
113
|
+
"2. You're authenticated: tailscale login\n " \
|
|
114
|
+
"3. The host is online: tailscale ping #{host}\n " \
|
|
115
|
+
"4. SSH works on its own: ssh #{host} true"
|
|
93
116
|
end
|
|
94
117
|
|
|
95
118
|
# Push image to multiple hosts via SSH
|
|
@@ -263,13 +286,69 @@ module Odysseus
|
|
|
263
286
|
end
|
|
264
287
|
end
|
|
265
288
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
289
|
+
# Runs a local command, streaming its output and refusing to wait forever.
|
|
290
|
+
#
|
|
291
|
+
# This was `` `#{cmd} 2>&1` ``, and all three of its properties were
|
|
292
|
+
# wrong for `docker pussh`. Backticks capture everything into a string,
|
|
293
|
+
# so a push printed nothing until it returned and a healthy one looked
|
|
294
|
+
# exactly like a stuck one. There was no timeout. And stdin stayed
|
|
295
|
+
# attached to the parent, so a child that asked a question waited on an
|
|
296
|
+
# answer that could never be typed into output nobody could see -- the
|
|
297
|
+
# reported symptom was a push that hung with no message at all.
|
|
298
|
+
#
|
|
299
|
+
# stdin is /dev/null so that any prompt reaches EOF and the command fails
|
|
300
|
+
# promptly instead of hanging. Prompting is not something odysseus can
|
|
301
|
+
# usefully do here anyway: SSH#connect is non-interactive for the same
|
|
302
|
+
# reason.
|
|
303
|
+
#
|
|
304
|
+
# @param timeout [Integer, nil] seconds before the command is killed
|
|
305
|
+
# @raise [Odysseus::BuildError] on failure, or on timeout
|
|
306
|
+
def execute_local_command(cmd, timeout: nil)
|
|
307
|
+
output = +''
|
|
308
|
+
|
|
309
|
+
# pgroup: true is not optional. Without it the child shares this
|
|
310
|
+
# process's group, and terminate's kill(-pgid) would signal odysseus
|
|
311
|
+
# itself -- which it duly did the first time this was run.
|
|
312
|
+
Open3.popen2e(cmd, pgroup: true) do |stdin, out, wait_thread|
|
|
313
|
+
# Closed immediately, not redirected: popen2e makes its own stdin
|
|
314
|
+
# pipe and ignores an `in:` option. Closing it is what gives a child
|
|
315
|
+
# that asks a question an EOF instead of an indefinite wait.
|
|
316
|
+
stdin.close
|
|
317
|
+
reader = Thread.new do
|
|
318
|
+
out.each_line do |line|
|
|
319
|
+
output << line
|
|
320
|
+
@logger.info(line.chomp)
|
|
321
|
+
end
|
|
322
|
+
rescue IOError
|
|
323
|
+
nil # the stream is closed under us when a timeout kills the child
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
unless timeout.nil? || wait_thread.join(timeout)
|
|
327
|
+
terminate(wait_thread.pid)
|
|
328
|
+
reader.join(1)
|
|
329
|
+
raise BuildError, "Command timed out after #{timeout}s: #{cmd}\n#{output}"
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
wait_thread.join
|
|
333
|
+
reader.join
|
|
334
|
+
raise BuildError, "Command failed: #{output}" unless wait_thread.value.success?
|
|
335
|
+
end
|
|
269
336
|
|
|
270
337
|
output
|
|
271
338
|
end
|
|
272
339
|
|
|
340
|
+
# The child is usually a shell, so signal the whole process group -- else
|
|
341
|
+
# `sh -c 'docker pussh ...'` dies and leaves the push running.
|
|
342
|
+
def terminate(pid)
|
|
343
|
+
Process.kill('TERM', -Process.getpgid(pid))
|
|
344
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
345
|
+
begin
|
|
346
|
+
Process.kill('TERM', pid)
|
|
347
|
+
rescue Errno::ESRCH
|
|
348
|
+
nil
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
273
352
|
def connect_to_build_host
|
|
274
353
|
Odysseus::Deployer::SSH.new(
|
|
275
354
|
host: @config[:host],
|
|
@@ -167,8 +167,7 @@ module Odysseus
|
|
|
167
167
|
rescue SocketError
|
|
168
168
|
raise Odysseus::SSHConnectionError, "Could not resolve hostname '#{@host}'. Check your DNS or /etc/hosts."
|
|
169
169
|
rescue Net::SSH::AuthenticationFailed
|
|
170
|
-
raise Odysseus::SSHAuthenticationError,
|
|
171
|
-
"SSH authentication failed for #{@user}@#{@host}. Check your SSH keys."
|
|
170
|
+
raise Odysseus::SSHAuthenticationError, authentication_failure_message
|
|
172
171
|
rescue Errno::ETIMEDOUT, Net::SSH::ConnectionTimeout, Errno::EHOSTUNREACH
|
|
173
172
|
error_msg = "Connection to #{@host} timed out."
|
|
174
173
|
if @use_tailscale
|
|
@@ -190,6 +189,29 @@ module Odysseus
|
|
|
190
189
|
raise Odysseus::SSHConnectionError, drop_message(open, e)
|
|
191
190
|
end
|
|
192
191
|
|
|
192
|
+
# A passphrase-protected key reaches here as an ordinary authentication
|
|
193
|
+
# failure. net-ssh sends the real reason to a logger rather than raising
|
|
194
|
+
# it (key_manager.rb:319) and odysseus configures none, so without this
|
|
195
|
+
# the reader is told to check keys that are entirely correct.
|
|
196
|
+
#
|
|
197
|
+
# The advice is ssh-agent rather than a prompt on purpose: odysseus
|
|
198
|
+
# connects with non_interactive: true, and a passphrase prompt on a
|
|
199
|
+
# deploy connection cannot be answered -- it would hang the deploy
|
|
200
|
+
# instead of failing it.
|
|
201
|
+
def authentication_failure_message
|
|
202
|
+
base = "SSH authentication failed for #{@user}@#{@host}."
|
|
203
|
+
locked = @keys.select { |key| Odysseus::SSHKey.encrypted?(File.expand_path(key)) }
|
|
204
|
+
|
|
205
|
+
return "#{base} Check your SSH keys." if locked.empty?
|
|
206
|
+
|
|
207
|
+
"#{base}\n\n" \
|
|
208
|
+
"#{locked.length == 1 ? 'This key is' : 'These keys are'} passphrase-protected, and odysseus " \
|
|
209
|
+
"never prompts for one:\n" \
|
|
210
|
+
"#{locked.map { |key| " #{key}" }.join("\n")}\n\n" \
|
|
211
|
+
"Add #{locked.length == 1 ? 'it' : 'them'} to your agent instead:\n" \
|
|
212
|
+
"#{locked.map { |key| " ssh-add #{key}" }.join("\n")}"
|
|
213
|
+
end
|
|
214
|
+
|
|
193
215
|
# @param open [Boolean, nil] whether the session was established
|
|
194
216
|
def drop_message(open, error)
|
|
195
217
|
return "Connection to #{@host} dropped mid-command rather than failing to open: #{error.message}" if open
|
|
@@ -9,6 +9,25 @@ module Odysseus
|
|
|
9
9
|
class Client
|
|
10
10
|
HEALTHCHECK_POLL_INTERVAL = 2 # seconds
|
|
11
11
|
|
|
12
|
+
# Applied to every container odysseus starts. Without them a container
|
|
13
|
+
# inherits the daemon's default logging, which on a stock Docker install
|
|
14
|
+
# is json-file with no size limit -- so a long-lived container fills the
|
|
15
|
+
# disk. Caddy is the clearest case, being the one container no deploy
|
|
16
|
+
# ever replaces, but a dependency that runs for months has the same
|
|
17
|
+
# problem, so this belongs on the shared path rather than on one caller.
|
|
18
|
+
#
|
|
19
|
+
# The driver is named rather than left to the daemon because max-size and
|
|
20
|
+
# max-file belong to json-file and local only: on a host defaulting to
|
|
21
|
+
# journald or syslog, passing them does not degrade gracefully, docker
|
|
22
|
+
# refuses the run. Naming it also makes explicit what the codebase
|
|
23
|
+
# already assumes -- `odysseus logs` shells out to `docker logs`, which
|
|
24
|
+
# most other drivers cannot answer.
|
|
25
|
+
#
|
|
26
|
+
# The cost, knowingly: a host with a deliberate daemon-wide log driver is
|
|
27
|
+
# overridden. Making these configurable is a separate TODO entry.
|
|
28
|
+
LOG_DRIVER = 'json-file'.freeze
|
|
29
|
+
LOG_OPTIONS = { 'max-size' => '100m', 'max-file' => '3' }.freeze
|
|
30
|
+
|
|
12
31
|
# @param ssh [Odysseus::Deployer::SSH] SSH connection to server
|
|
13
32
|
def initialize(ssh)
|
|
14
33
|
@ssh = ssh
|
|
@@ -468,6 +487,14 @@ module Odysseus
|
|
|
468
487
|
nil
|
|
469
488
|
end
|
|
470
489
|
|
|
490
|
+
# Extracted from build_run_command rather than inlined there: the flags
|
|
491
|
+
# are fixed, and the method was already at RuboCop's complexity ceiling.
|
|
492
|
+
# @return [Array<String>] the --log-driver and --log-opt arguments
|
|
493
|
+
def log_args
|
|
494
|
+
["--log-driver #{LOG_DRIVER}"] +
|
|
495
|
+
LOG_OPTIONS.map { |key, value| "--log-opt #{key}=#{value}" }
|
|
496
|
+
end
|
|
497
|
+
|
|
471
498
|
def build_run_command(name:, image:, options:, env_file: nil)
|
|
472
499
|
parts = ['docker run -d']
|
|
473
500
|
|
|
@@ -525,6 +552,10 @@ module Odysseus
|
|
|
525
552
|
# Restart policy
|
|
526
553
|
parts << "--restart #{options[:restart] || 'unless-stopped'}"
|
|
527
554
|
|
|
555
|
+
# Log rotation (see LOG_DRIVER/LOG_OPTIONS). Before the image, or
|
|
556
|
+
# docker would hand them to the container's entrypoint instead.
|
|
557
|
+
parts.concat(log_args)
|
|
558
|
+
|
|
528
559
|
# Image
|
|
529
560
|
parts << image
|
|
530
561
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# lib/odysseus/ssh_key.rb
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module Odysseus
|
|
6
|
+
# Reads what can be known about a private key file without decrypting it.
|
|
7
|
+
#
|
|
8
|
+
# This exists for one message. odysseus connects with `non_interactive: true`
|
|
9
|
+
# -- a passphrase prompt on a deploy connection can never be answered, so
|
|
10
|
+
# prompting would hang rather than help. net-ssh reports the resulting
|
|
11
|
+
# key-loading failure to a logger rather than raising it
|
|
12
|
+
# (`key_manager.rb:319`), so with no logger configured the reason is
|
|
13
|
+
# discarded and authentication simply fails. The user is then told to check
|
|
14
|
+
# keys that are perfectly correct. Knowing which key is locked is what turns
|
|
15
|
+
# that into an answerable question.
|
|
16
|
+
module SSHKey
|
|
17
|
+
OPENSSH_HEADER = '-----BEGIN OPENSSH PRIVATE KEY-----'.freeze
|
|
18
|
+
OPENSSH_MAGIC = "openssh-key-v1\0".freeze
|
|
19
|
+
PEM_ENCRYPTED = /^Proc-Type:\s*4,ENCRYPTED/
|
|
20
|
+
NO_CIPHER = 'none'.freeze
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
# @param path [String] path to a private key file
|
|
24
|
+
# @return [Boolean] true only when the file is a private key that is
|
|
25
|
+
# passphrase-protected. False for anything unreadable, absent, public,
|
|
26
|
+
# or not a key: those are different failures with different messages,
|
|
27
|
+
# and claiming a passphrase problem for them would send the reader
|
|
28
|
+
# somewhere just as wrong as "check your SSH keys".
|
|
29
|
+
def encrypted?(path)
|
|
30
|
+
data = read(path)
|
|
31
|
+
return false unless data
|
|
32
|
+
|
|
33
|
+
data.include?(OPENSSH_HEADER) ? openssh_encrypted?(data) : PEM_ENCRYPTED.match?(data)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def read(path)
|
|
39
|
+
return nil unless File.file?(path)
|
|
40
|
+
|
|
41
|
+
File.read(path)
|
|
42
|
+
rescue SystemCallError, IOError
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# An OpenSSH-format key states its cipher in the binary body rather than
|
|
47
|
+
# in a header: the magic "openssh-key-v1\0", then a 4-byte big-endian
|
|
48
|
+
# length and that many bytes of cipher name, which is "none" when the key
|
|
49
|
+
# is not encrypted. Read rather than decrypted, so no passphrase is
|
|
50
|
+
# needed to answer the question.
|
|
51
|
+
def openssh_encrypted?(data)
|
|
52
|
+
body = data[/#{Regexp.escape(OPENSSH_HEADER)}\n(.*?)\n-----END/m, 1]
|
|
53
|
+
return false unless body
|
|
54
|
+
|
|
55
|
+
raw = Base64.decode64(body)
|
|
56
|
+
return false unless raw.start_with?(OPENSSH_MAGIC)
|
|
57
|
+
|
|
58
|
+
rest = raw.byteslice(OPENSSH_MAGIC.bytesize..) || ''
|
|
59
|
+
return false if rest.bytesize < 4
|
|
60
|
+
|
|
61
|
+
length = rest.byteslice(0, 4).unpack1('N')
|
|
62
|
+
cipher = rest.byteslice(4, length)
|
|
63
|
+
|
|
64
|
+
!cipher.nil? && cipher != NO_CIPHER
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/odysseus.rb
CHANGED
|
@@ -9,6 +9,7 @@ module Odysseus
|
|
|
9
9
|
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
|
10
10
|
loader.inflector.inflect(
|
|
11
11
|
'ssh' => 'SSH',
|
|
12
|
+
'ssh_key' => 'SSHKey',
|
|
12
13
|
'aws_asg' => 'AwsAsg'
|
|
13
14
|
)
|
|
14
15
|
# errors.rb doesn't follow Zeitwerk conventions (plural, defines multiple classes)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: odysseus-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.10.
|
|
4
|
+
version: 0.10.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Thomas
|
|
@@ -65,6 +65,34 @@ dependencies:
|
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '7.2'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: bcrypt_pbkdf
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.1'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.1'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: ed25519
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '1.3'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '1.3'
|
|
68
96
|
- !ruby/object:Gem::Dependency
|
|
69
97
|
name: zeitwerk
|
|
70
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -131,6 +159,7 @@ files:
|
|
|
131
159
|
- lib/odysseus/setup/escalation.rb
|
|
132
160
|
- lib/odysseus/setup/preparer.rb
|
|
133
161
|
- lib/odysseus/setup/public_key.rb
|
|
162
|
+
- lib/odysseus/ssh_key.rb
|
|
134
163
|
- lib/odysseus/validators/config.rb
|
|
135
164
|
- lib/odysseus/version_resolver.rb
|
|
136
165
|
- sig/odysseus/core.rbs
|