herd-rb 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84702715cdf7b1c28fe203c5c88a0b0f7a3c0a77bf5bfb237c027a82585be7c0
4
- data.tar.gz: d61aa1dcbe955f6ab0bd56c535612cf490891ed2589425af1ac23baf6e085636
3
+ metadata.gz: f0b080c1095b56bac42536acc7df54cd20c97bac52f2367301e2b951eb8a60be
4
+ data.tar.gz: 397b3c9636a2dc20310f8e70fbe3911049bde76183fe8650f30d0cff4bb40402
5
5
  SHA512:
6
- metadata.gz: c2f6c111802762b7c5e642a1a8f073d9bad60c138f5d607870a4989154777aa2691997ca514ef440237d73b4112107a72dffe17b1c3221aab0d7663440be70bf
7
- data.tar.gz: b65a40974cee1729989bfc9c65a95b937e101c71072daa2da4cf70475cd04ce8de65ab4483e1dfbf4301bcc27a4bcbe149a9c07119619190bde82f052c1eee1e
6
+ metadata.gz: 514947c517caa05fc2229c6d293042bf6065ad2d067574087d479772fac271802279f769a6f98d33ae0c7a09720bd99e6592b475e2e6cc3934c515d970323bc1
7
+ data.tar.gz: 7962d18779bc695f3858adcbf40dc75350e9d88ec00a29b0ab71b8e9ab8ffd21b18f28e8078c2e012b33612c400941d7394ce5254fa52520be8988bc59d11784
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.2
data/README.md CHANGED
@@ -4,9 +4,9 @@ Fast host configuration tool.
4
4
 
5
5
  ## TODO
6
6
 
7
- * [ ] Commands with arguments
7
+ * [x] Run with `sudo`
8
+ * [x] Commands with arguments
8
9
  * [ ] Reading and writing files
9
- * [ ] Run with `sudo`
10
10
 
11
11
  ## Installation
12
12
 
@@ -55,6 +55,25 @@ runner.exec("hostname") # ["alpha001\n", "omega001\n"]
55
55
  runner.exec { hostname + uptime } # ["alpha001\n2000 years\n", "omega001\2500 years\n"]
56
56
  ```
57
57
 
58
+ ### Something more complex
59
+
60
+ ```ruby
61
+ public_key_path = File.expand_path("~/.ssh/id_ed25519.pub")
62
+ my_key = File.read(public_key_path).chomp
63
+
64
+ result = runner.exec do
65
+ h = hostname
66
+ keys = authorized_keys
67
+
68
+ if keys.include?(my_key)
69
+ puts "Key already in authorized_keys on host #{h}"
70
+ else
71
+ add_authorized_key my_key
72
+ puts "Added new key for host #{h}"
73
+ end
74
+ end
75
+ ```
76
+
58
77
  ## Development
59
78
 
60
79
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/TODO.md ADDED
@@ -0,0 +1,20 @@
1
+ # TODO
2
+
3
+ - [x] Load session commands automatically from `lib/herd/session/commands`.
4
+ - [x] Extract existing `Session` command helpers (e.g., `add_authorized_key`) into the commands directory.
5
+ - [x] Ensure new command loading approach is covered by specs.
6
+ - [ ] Add explicit `logger` dependency to address net-ssh warning on Ruby 3.4.2.
7
+ - [ ] Document new command-extension flow with YARD annotations.
8
+
9
+ # Log
10
+
11
+ - Initialized planning file and seeded initial task list.
12
+ - Updated project configuration to target Ruby 3.4.2 (Gemfile, gemspec, .ruby-version, lockfile).
13
+ - Synced RuboCop config with Ruby 3.4.2 and confirmed clean lint run.
14
+ - Moved development dependencies from gemspec to Gemfile; bundle, lint, and tests still pass (with net-ssh logger warning).
15
+ - Fixed `Session#method_missing` to avoid extra spaces when building commands; specs now green.
16
+ - Implemented automatic session command loading via prepended modules, moved authorized key helpers, added YARD dependency, and expanded specs accordingly.
17
+
18
+ # Notes
19
+
20
+ - Keep command implementations in English and back them with tests.
data/lib/herd/host.rb CHANGED
@@ -5,7 +5,7 @@ require "net/ssh"
5
5
  module Herd
6
6
  # Target host
7
7
  class Host
8
- attr_reader :host, :user, :ssh_options
8
+ attr_reader :host, :user, :ssh_options, :password
9
9
 
10
10
  def initialize(host, user, port: 22, private_key_path: nil, password: nil)
11
11
  @host = host
@@ -17,15 +17,18 @@ module Herd
17
17
  else
18
18
  @ssh_options[:password] = password
19
19
  end
20
+
21
+ @password = password
20
22
  end
21
23
 
22
- def exec(command = nil, &block)
24
+ def exec(command = nil, &)
23
25
  Net::SSH.start(host, user, ssh_options) do |ssh|
24
- session = Herd::Session.new(ssh)
26
+ session = Herd::Session.new(ssh, password)
25
27
 
26
28
  output = nil
27
29
  output = session.send(command) if command
28
- output = session.instance_exec(&block) if block_given?
30
+ output = session.instance_exec(&) if block_given?
31
+
29
32
  output
30
33
  end
31
34
  end
data/lib/herd/runner.rb CHANGED
@@ -9,9 +9,9 @@ module Herd
9
9
  @hosts = hosts
10
10
  end
11
11
 
12
- def exec(command = nil, &block)
12
+ def exec(command = nil, &)
13
13
  threads = hosts.map do |host|
14
- Thread.new { host.exec(command, &block) }
14
+ Thread.new { host.exec(command, &) }
15
15
  end
16
16
 
17
17
  threads.each(&:join)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Herd
4
+ module SessionCommands
5
+ # Commands for inspecting and managing authorized SSH keys.
6
+ module AuthorizedKeys
7
+ def authorized_keys
8
+ cat("~/.ssh/authorized_keys")&.chomp&.split("\n") || []
9
+ end
10
+
11
+ def add_authorized_key(key)
12
+ touch("~/.ssh/authorized_keys")
13
+ chmod("600 ~/.ssh/authorized_keys")
14
+ echo("'#{key}' >> ~/.ssh/authorized_keys")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Herd
4
+ module SessionCommands
5
+ # Working with package managers, like apt for Ubuntu
6
+ module Packages
7
+ def install_packages(packages)
8
+ packages = [packages].flatten.join(" ")
9
+ echo %(-e '#{password}\n' | sudo -S apt install -qq -y #{packages})
10
+ end
11
+ end
12
+ end
13
+ end
data/lib/herd/session.rb CHANGED
@@ -3,37 +3,82 @@
3
3
  module Herd
4
4
  # Session for executing commands on the remote host
5
5
  class Session
6
- COMMANDS = %i[cat chmod echo hostname touch].freeze
6
+ OS_COMMANDS = %i[cat chmod echo hostname touch].freeze
7
+ CUSTOM_COMMANDS_DIR = File.expand_path("session/commands", __dir__)
7
8
 
8
- attr_reader :ssh
9
+ attr_reader :ssh, :password
9
10
 
10
- def initialize(ssh)
11
+ def initialize(ssh, password = nil)
11
12
  @ssh = ssh
13
+ @password = password
12
14
  end
13
15
 
14
- def authorized_keys
15
- cat("~/.ssh/authorized_keys")&.chomp&.split("\n") || []
16
+ def method_missing(cmd, *args)
17
+ command_parts = [cmd.to_s]
18
+ command_parts.concat(args.map(&:to_s)) if args.any?
19
+ command = command_parts.join(" ")
20
+
21
+ run(command)
16
22
  end
17
23
 
18
- def add_authorized_key(key)
19
- touch("~/.ssh/authorized_keys")
20
- chmod("600 ~/.ssh/authorized_keys")
21
- echo "'#{key}' >> ~/.ssh/authorized_keys"
24
+ def run(command)
25
+ result = []
26
+ ssh.open_channel do |channel|
27
+ channel.request_pty do |ch, success|
28
+ raise ::Herd::CommandError, "could not obtain pty" unless success
29
+
30
+ channel_run(ch, command, result)
31
+ end
32
+ end
33
+ ssh.loop
34
+ result.join
22
35
  end
23
36
 
24
- def method_missing(cmd, *args)
25
- command = cmd.to_s
26
- command = "#{command} #{args.join(" ")}" if args
37
+ def respond_to_missing?(cmd)
38
+ OS_COMMANDS.include?(cmd) || super
39
+ end
40
+
41
+ class << self
42
+ def load_command_modules
43
+ command_files.each { |file| require file }
27
44
 
28
- ssh.exec! command do |_, stream, data|
29
- raise ::Herd::CommandError, data if stream == :stderr
45
+ session_command_modules.each do |mod|
46
+ next if self <= mod
30
47
 
31
- return data
48
+ prepend mod
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def command_files
55
+ Dir[File.join(CUSTOM_COMMANDS_DIR, "*.rb")]
56
+ end
57
+
58
+ def session_command_modules
59
+ return [] unless defined?(Herd::SessionCommands)
60
+
61
+ Herd::SessionCommands.constants
62
+ .sort
63
+ .map { |const_name| Herd::SessionCommands.const_get(const_name) }
64
+ .select { |value| value.is_a?(Module) }
32
65
  end
33
66
  end
34
67
 
35
- def respond_to_missing?(cmd)
36
- COMMANDS.include?(cmd) || super
68
+ private
69
+
70
+ def channel_run(channel, command, result)
71
+ channel.exec(command) do |c, _|
72
+ c.on_data do |_, data|
73
+ result << data
74
+ end
75
+
76
+ c.on_extended_data do |_, _, data|
77
+ raise ::Herd::CommandError, data
78
+ end
79
+ end
37
80
  end
38
81
  end
39
82
  end
83
+
84
+ Herd::Session.load_command_modules
data/lib/herd/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Herd
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,70 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: herd-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yury Kotlyarov
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: rspec
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: '3.0'
19
- type: :development
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: '3.0'
26
- - !ruby/object:Gem::Dependency
27
- name: rubocop
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '1.21'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '1.21'
40
- - !ruby/object:Gem::Dependency
41
- name: rubocop-rake
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: '0'
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: '0'
54
- - !ruby/object:Gem::Dependency
55
- name: rubocop-rspec
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: '0'
61
- type: :development
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: '0'
11
+ dependencies: []
68
12
  description: |
69
13
  Simple ruby DSL for fast host configuration. Supports Ubuntu and requires
70
14
  SSH server running on each targets host.
@@ -74,14 +18,18 @@ executables: []
74
18
  extensions: []
75
19
  extra_rdoc_files: []
76
20
  files:
21
+ - ".ruby-version"
77
22
  - CODE_OF_CONDUCT.md
78
23
  - LICENSE.txt
79
24
  - README.md
80
25
  - Rakefile
26
+ - TODO.md
81
27
  - lib/herd.rb
82
28
  - lib/herd/host.rb
83
29
  - lib/herd/runner.rb
84
30
  - lib/herd/session.rb
31
+ - lib/herd/session/commands/authorized_keys.rb
32
+ - lib/herd/session/commands/packages.rb
85
33
  - lib/herd/version.rb
86
34
  - sig/herd.rbs
87
35
  homepage: https://github.com/yura/herd
@@ -91,6 +39,7 @@ metadata:
91
39
  allowed_push_host: https://rubygems.org
92
40
  homepage_uri: https://github.com/yura/herd
93
41
  source_code_uri: https://github.com/yura/herd
42
+ rubygems_mfa_required: 'true'
94
43
  rdoc_options: []
95
44
  require_paths:
96
45
  - lib
@@ -98,7 +47,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
98
47
  requirements:
99
48
  - - ">="
100
49
  - !ruby/object:Gem::Version
101
- version: 3.2.0
50
+ version: 3.4.2
102
51
  required_rubygems_version: !ruby/object:Gem::Requirement
103
52
  requirements:
104
53
  - - ">="