eaton 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: 28bb3b2bbbe4ecc0ad0b791242d1c9e059a832e5efc056b59b3567a30bf4e0dd
4
- data.tar.gz: 243a22e2fc3b36accc7f3b01090a4ce53d8309125b86948d9e84977581c80081
3
+ metadata.gz: eb4213ff1de1459504fd9aef3ff717e711d4926dcc482af271a5ced054c4dd72
4
+ data.tar.gz: 5cb7bf81a8fc1cd3cba0d27407f352731bb49a07a22e210b87ddcd6fffe5eb9d
5
5
  SHA512:
6
- metadata.gz: 81f8dec09135247929258c73eadce8baecd45752dd9026d789b7bfb73a1033afb59fbfa75b40bcef3b7b8bd49c3747833781fda22bf9d1bfd72c01648b2d9492
7
- data.tar.gz: 00f421eff32a8cdcac89de7433ec744de4cd85616e069ad6ff9069923904762d687768739860d43354b5775e1d31474502cfb020fa064b8d2f0685012449dbea
6
+ metadata.gz: 5d64daf3ed46a838ee8e1b0b94501122af4b4124c1c9fe934fab670d504268ccf7809470690d69ce102d4ccfa5cde454e03b9f4585444d54b6ffbe1379d70a18
7
+ data.tar.gz: 8bb820f4d75d9149196d12da53af22e8b75dbd2521894c9b8cfa6d64d09976f0a72c1a35818553ce5a66203b9aa91d86596a486b915c2df9cd60de28c900a72a
data/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2026-09-10
9
+
10
+ ### Added
11
+ - `Client#put` for settings writes; the client could only read, POST and DELETE
12
+ - `Eaton::Network`, mixed into `Client`:
13
+ - `#ipv4` reports the address, mask, gateway and mode eth0 is running on
14
+ - `#set_ipv4(address:, subnet_mask:, gateway:)` switches eth0 to a static
15
+ address. All three are required and a blank one raises `ArgumentError`:
16
+ the PDU applies them together, and a wrong mask can leave it unreachable.
17
+
18
+ ### Improved
19
+ - Enhanced authentication error handling for expired credentials
20
+ - Added detailed password policy requirements in error messages
21
+ - Improved user guidance for first-time login and password expiration scenarios
22
+ - Error messages now include direct instructions for changing password via web interface
23
+
8
24
  ## [0.1.0] - 2025-01-20
9
25
 
10
26
  ### Added
data/TODO.md ADDED
@@ -0,0 +1,81 @@
1
+ # TODO
2
+
3
+ ## Future Enhancements
4
+
5
+ ### Programmatic Password Change (Issue #1)
6
+
7
+ **Priority**: Medium
8
+ **Context**: First-time login and password expiration handling
9
+
10
+ **Problem**:
11
+ Eaton PDUs enforce password expiration policies. When credentials expire, users currently must:
12
+ 1. Navigate to the PDU web interface
13
+ 2. Manually change the password
14
+ 3. Update credentials in their application
15
+
16
+ This creates friction, especially for:
17
+ - First-time setup (initial default password change)
18
+ - Automated systems that need to handle password rotation
19
+ - Multiple PDU management scenarios
20
+
21
+ **Proposed Solution**:
22
+ Add programmatic password change capability to the client:
23
+
24
+ ```ruby
25
+ # Ruby API
26
+ client = Eaton::Client.new(
27
+ host: 'pdu.example.com',
28
+ username: 'admin',
29
+ password: 'current_password'
30
+ )
31
+
32
+ client.change_password(new_password: 'NewSecurePass123!')
33
+
34
+ # CLI
35
+ eaton change-password \
36
+ --host pdu.example.com \
37
+ --username admin \
38
+ --current-password old_pass \
39
+ --new-password new_pass
40
+ ```
41
+
42
+ **Implementation Requirements**:
43
+ 1. Research Eaton PDU API endpoint for password changes
44
+ 2. Implement `change_password` method in `Eaton::Client`
45
+ 3. Add password policy validation before attempting change
46
+ 4. Create CLI command `eaton change-password`
47
+ 5. Handle special case: changing expired credentials
48
+ 6. Add comprehensive error handling
49
+ 7. Update documentation with password management guide
50
+ 8. Add tests for password change functionality
51
+
52
+ **API Research Needed**:
53
+ - Identify correct REST endpoint (likely `/users/{id}` or `/accounts/{id}`)
54
+ - Determine if special authentication is needed for expired credentials
55
+ - Understand session requirements during password change
56
+ - Check if password history/reuse policies are enforced
57
+
58
+ **Security Considerations**:
59
+ - Never log passwords
60
+ - Clear old password from memory after use
61
+ - Validate new password against policy before submitting
62
+ - Handle authentication state correctly during transition
63
+ - Consider secure input methods (no command-line password echo)
64
+
65
+ **Testing Approach**:
66
+ - Mock API responses for password change
67
+ - Test policy validation
68
+ - Test error scenarios (wrong current password, policy violations)
69
+ - Integration test with real PDU (manual, not CI)
70
+
71
+ **Documentation Updates**:
72
+ - Add password management section to README
73
+ - Include examples for both API and CLI
74
+ - Document password policy requirements
75
+ - Add troubleshooting guide for common scenarios
76
+
77
+ **Related Files**:
78
+ - `lib/eaton/client.rb` - Add change_password method
79
+ - `lib/eaton/cli.rb` - Add CLI command
80
+ - `spec/*` - Add test coverage
81
+ - `README.md` - Update documentation
data/lib/eaton/client.rb CHANGED
@@ -11,6 +11,7 @@ module Eaton
11
11
  class APIError < StandardError; end
12
12
 
13
13
  include Power
14
+ include Network
14
15
 
15
16
  attr_reader :host, :username, :base_url
16
17
 
@@ -39,7 +40,7 @@ module Eaton
39
40
  @session = data["session"]
40
41
  @token
41
42
  else
42
- raise AuthenticationError, "Authentication failed: #{response.body}"
43
+ handle_auth_error(response)
43
44
  end
44
45
  rescue JSON::ParserError => e
45
46
  raise AuthenticationError, "Invalid response from server: #{e.message}"
@@ -68,6 +69,16 @@ module Eaton
68
69
  handle_response(execute_request(request))
69
70
  end
70
71
 
72
+ def put(path, data = {})
73
+ authenticate! unless authenticated?
74
+
75
+ request = Net::HTTP::Put.new("#{@base_path}#{path}")
76
+ add_auth_headers(request)
77
+ request.body = data.to_json
78
+
79
+ handle_response(execute_request(request))
80
+ end
81
+
71
82
  def logout
72
83
  return unless authenticated?
73
84
 
@@ -83,6 +94,65 @@ module Eaton
83
94
 
84
95
  private
85
96
 
97
+ def handle_auth_error(response)
98
+ begin
99
+ error_data = JSON.parse(response.body)
100
+
101
+ # Check for expired credentials error
102
+ if error_data["code"] == 112 && error_data["description"] =~ /expired/i
103
+ policy = parse_password_policy(error_data["args"])
104
+ message = build_expired_credentials_message(policy)
105
+ raise AuthenticationError, message
106
+ end
107
+ rescue JSON::ParserError
108
+ # Fall through to generic error
109
+ end
110
+
111
+ # Generic authentication error
112
+ raise AuthenticationError, "Authentication failed: #{response.body}"
113
+ end
114
+
115
+ def parse_password_policy(args)
116
+ return nil unless args && args.is_a?(Array) && args.length >= 7
117
+
118
+ {
119
+ min_length: args[0],
120
+ max_length: args[1],
121
+ require_uppercase: args[2] == 1,
122
+ require_lowercase: args[3] == 1,
123
+ require_numbers: args[4] == 1,
124
+ require_special: args[5] == 1,
125
+ special_chars: args[6]
126
+ }
127
+ end
128
+
129
+ def build_expired_credentials_message(policy)
130
+ message = "Credentials are expired. Please change the password via the PDU web interface.\n\n"
131
+
132
+ if policy
133
+ message += "Password requirements:\n"
134
+ message += " - Length: #{policy[:min_length]}-#{policy[:max_length]} characters\n"
135
+
136
+ requirements = []
137
+ requirements << "uppercase letters" if policy[:require_uppercase]
138
+ requirements << "lowercase letters" if policy[:require_lowercase]
139
+ requirements << "numbers" if policy[:require_numbers]
140
+ requirements << "special characters" if policy[:require_special]
141
+
142
+ message += " - Must include: #{requirements.join(', ')}\n" if requirements.any?
143
+ message += " - Allowed special characters: #{policy[:special_chars]}\n" if policy[:special_chars]
144
+ message += "\n"
145
+ end
146
+
147
+ message += "To change password:\n"
148
+ message += " 1. Navigate to https://#{@host}#{@port == 443 ? '' : ":#{@port}"}\n"
149
+ message += " 2. Log in with current credentials\n"
150
+ message += " 3. Follow prompts to change password\n"
151
+ message += "\nNote: First-time login may require password change before API access is granted."
152
+
153
+ message
154
+ end
155
+
86
156
  def http_connection
87
157
  @http_connection ||= begin
88
158
  http = Net::HTTP.new(@host, @port)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eaton
4
+ # eth0 addressing on the PDU's network management card.
5
+ module Network
6
+ ETH0_PATH = "/managers/1/networkService/networkInterfaces/eth0"
7
+
8
+ # Current eth0 addressing. :mode is "dhcp client" or "manual"; the address,
9
+ # subnet_mask and gateway are what the interface is running on right now,
10
+ # which is what DHCP handed it when the mode is dhcp.
11
+ def ipv4
12
+ data = get(ETH0_PATH)
13
+
14
+ {
15
+ mode: data.dig("ipv4", "settings", "mode"),
16
+ address: data.dig("ipv4", "status", "address"),
17
+ subnet_mask: data.dig("ipv4", "status", "subnetMask"),
18
+ gateway: data.dig("ipv4", "status", "gateway")
19
+ }
20
+ end
21
+
22
+ # Switch eth0 to a static address. All three values are required: the PDU
23
+ # applies them together, and a wrong mask strands it on the wrong subnet.
24
+ def set_ipv4(address:, subnet_mask:, gateway:)
25
+ raise ArgumentError, "address, subnet_mask and gateway are all required" if
26
+ [address, subnet_mask, gateway].any? { |v| v.to_s.strip.empty? }
27
+
28
+ put(ETH0_PATH, ipv4: {
29
+ settings: {
30
+ enabled: true,
31
+ mode: "manual",
32
+ manual: { address: address, subnetMask: subnet_mask, gateway: gateway }
33
+ }
34
+ })
35
+ end
36
+ end
37
+ end
data/lib/eaton/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Eaton
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/eaton.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "eaton/version"
4
4
  require_relative "eaton/power"
5
+ require_relative "eaton/network"
5
6
  require_relative "eaton/client"
6
7
  require_relative "eaton/cli"
7
8
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eaton
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
  - Jonathan Siegel
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-10-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: thor
@@ -29,7 +28,7 @@ description: Comprehensive power monitoring and management for Eaton Rack PDU G4
29
28
  detailed metrics (voltage, current, power factor), OAuth2 authentication, and SSH
30
29
  tunneling support.
31
30
  email:
32
- - jonathan@example.com
31
+ - "<248302+usiegj00@users.noreply.github.com>"
33
32
  executables:
34
33
  - eaton
35
34
  extensions: []
@@ -39,10 +38,12 @@ files:
39
38
  - LICENSE
40
39
  - README.md
41
40
  - Rakefile
41
+ - TODO.md
42
42
  - exe/eaton
43
43
  - lib/eaton.rb
44
44
  - lib/eaton/cli.rb
45
45
  - lib/eaton/client.rb
46
+ - lib/eaton/network.rb
46
47
  - lib/eaton/power.rb
47
48
  - lib/eaton/version.rb
48
49
  - sig/pdu_manager.rbs
@@ -55,7 +56,6 @@ metadata:
55
56
  bug_tracker_uri: https://github.com/usiegj00/eaton/issues
56
57
  changelog_uri: https://github.com/usiegj00/eaton/blob/main/CHANGELOG.md
57
58
  documentation_uri: https://github.com/usiegj00/eaton
58
- post_install_message:
59
59
  rdoc_options: []
60
60
  require_paths:
61
61
  - lib
@@ -70,8 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
70
  - !ruby/object:Gem::Version
71
71
  version: '0'
72
72
  requirements: []
73
- rubygems_version: 3.5.22
74
- signing_key:
73
+ rubygems_version: 3.6.9
75
74
  specification_version: 4
76
75
  summary: Ruby gem and CLI for managing Eaton Rack PDU G4 devices via REST API
77
76
  test_files: []