Skip to content

Commit c41bba9

Browse files
authored
Merge pull request #937 from Sepehr-A/issue-845
Make username also an env var for poller
2 parents ced4eb6 + 22adb0a commit c41bba9

File tree

3 files changed

+81
-8
lines changed

3 files changed

+81
-8
lines changed

docs/inventory.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ auths:
6969
- name: suzieq-user-04
7070
key-passphrase: ask
7171
keyfile: path/to/key
72+
73+
- name: suzieq-user-05
74+
username: ask
75+
password: ask
76+
77+
- name: suzieq-user-06
78+
username: env:USERNAME_ENV_VAR
79+
password: ask
80+
81+
- name: suzieq-user-07
82+
username: env:USERNAME_ENV_VAR
83+
password: env:PASSWORD_ENV_VAR
84+
85+
- name: suzieq-user-08
86+
username: ask
87+
password: env:PASSWORD_ENV_VAR
7288

7389
namespaces:
7490
- name: testing
@@ -80,7 +96,7 @@ namespaces:
8096
!!! warning
8197
Some observations on the YAML file above:
8298
83-
- **This is just an example** that covers all the possible combinations, **not an real life inventory**
99+
- **This is just an example** that covers most of the possible combinations, **not an real life inventory**
84100
- **Do not specify device type unless you're using REST**. SuzieQ automatically determines device type with SSH
85101
- Most environments require setting the `ignore-known-hosts` option in the device section
86102
- The auths section shows all the different authorization methods supported by SuzieQ
@@ -95,7 +111,8 @@ For this reason, SuzieQ inventory now supports three different options to store
95111
- `env:<ENV_VARIABLE>`: the sensitive information is stored in an environment variable
96112
- `ask`: the user can write the sensitive information on the stdin
97113

98-
Currently this method is used to specify passwords, passphrases and tokens.
114+
This method is currently utilized for specifying usernames, passwords,
115+
passphrases, and tokens.
99116

100117
## <a name='inventory-sources'></a>Sources
101118

@@ -323,8 +340,10 @@ In case a private key is used to authenticate:
323340

324341
Where `key-passphrase` is the passphrase of the private key.
325342

326-
Both `passoword` and `key-passphrase` are considered [sensitive data](#sensitive-data).
327-
For this reason they can be set as plaintext, env variable or asked to the user via stdin.
343+
`Password`, `key-passphrase` and `username` are considered [sensitive
344+
data](#sensitive-data).
345+
For this reason they can be set as plaintext, env variable or
346+
asked to the user via stdin.
328347

329348
### <a name='cred-file'></a>Credential file
330349

suzieq/poller/controller/credential_loader/static.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
from suzieq.poller.controller.credential_loader.base_credential_loader import \
77
CredentialLoader, CredentialLoaderModel, check_credentials
8-
from suzieq.shared.utils import get_sensitive_data
98
from suzieq.shared.exceptions import SensitiveLoadError, InventorySourceError
9+
from suzieq.shared.utils import get_sensitive_data
1010

1111

1212
class StaticModel(CredentialLoaderModel):
@@ -18,7 +18,7 @@ class StaticModel(CredentialLoaderModel):
1818
keyfile: Optional[str]
1919
enable_password: Optional[str] = Field(alias='enable-password')
2020

21-
@validator('password', 'key_passphrase', 'enable_password')
21+
@validator('username', 'password', 'key_passphrase', 'enable_password')
2222
def validate_sens_field(cls, field):
2323
"""Validate if the sensitive var was passed correctly
2424
"""
@@ -57,6 +57,14 @@ def init(self, init_data: dict):
5757
init_data['key_passphrase'] = init_data.pop('key-passphrase', None)
5858
super().init(init_data)
5959

60+
if self._data.username == 'ask':
61+
try:
62+
self._data.username = get_sensitive_data(
63+
self._data.username,
64+
f'{self.name} Username to login to device: ')
65+
except SensitiveLoadError as e:
66+
raise InventorySourceError(f'{self.name} {e}')
67+
6068
if self._data.password == 'ask':
6169
try:
6270
self._data.password = get_sensitive_data(

tests/unit/poller/controller/credential_loader/static/test_static_loader.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from typing import Dict
2-
from pydantic import ValidationError
32

43
import pytest
4+
from pydantic import ValidationError
5+
56
from suzieq.poller.controller.credential_loader.static import (StaticLoader,
67
StaticModel)
78
from tests.unit.poller.shared.utils import read_yaml_file
@@ -84,30 +85,38 @@ def test_variables_init(monkeypatch):
8485
"""
8586
ask_password = 'ask_password'
8687
env_password = 'env_password'
88+
ask_username = 'ask_username'
89+
env_username = 'env_username'
8790
plain_passphrase = 'my-pass'
8891

8992
# 'env' and 'plain' values
9093
init_data = {
9194
'name': 'n',
95+
'username': 'env:SUZIEQ_ENV_USERNAME',
9296
'key-passphrase': f'plain:{plain_passphrase}',
9397
'password': 'env:SUZIEQ_ENV_PASSWORD'
9498
}
9599
monkeypatch.setenv('SUZIEQ_ENV_PASSWORD', env_password)
100+
monkeypatch.setenv('SUZIEQ_ENV_USERNAME', env_username)
96101
sl = StaticModel(**init_data)
97102

98103
assert sl.key_passphrase == plain_passphrase
99104
assert sl.password == env_password
105+
assert sl.username == env_username
100106

101107
# 'ask' values
102108
init_data = {
103109
'name': 'n',
110+
'username': 'ask',
104111
'password': 'ask'
105112
}
106113
valid_data = StaticModel(**init_data).dict(by_alias=True)
107-
monkeypatch.setattr('getpass.getpass', lambda x: ask_password)
114+
mock_get_pass = MockGetPass([ask_username, ask_password])
115+
monkeypatch.setattr('getpass.getpass', mock_get_pass)
108116
sl = StaticLoader(valid_data)
109117

110118
assert sl._data.password == ask_password
119+
assert sl._data.username == ask_username
111120

112121
# unknown parameter
113122
init_data = {
@@ -117,3 +126,40 @@ def test_variables_init(monkeypatch):
117126
}
118127
with pytest.raises(ValidationError):
119128
StaticModel(**init_data)
129+
130+
131+
class MockGetPass:
132+
"""
133+
Mocks `getpass.getpass` for testing, cycling through a list of predefined
134+
responses.
135+
136+
Attributes:
137+
responses (list): A list of responses to simulate sequential user inputs.
138+
call_count (int): Tracks calls to provide the next response in the list.
139+
"""
140+
141+
def __init__(self, responses: list):
142+
"""
143+
Initializes the mock with responses and resets call count.
144+
145+
Args:
146+
responses: Simulated user inputs.
147+
"""
148+
self.responses = responses
149+
self.call_count = 0
150+
151+
def __call__(self, prompt=''):
152+
"""
153+
Returns the next response from the list, mimicking user input.
154+
155+
Args:
156+
prompt: Unused, present for compatibility.
157+
158+
Returns:
159+
The next simulated user input.
160+
"""
161+
response = self.responses[self.call_count]
162+
self.call_count += 1
163+
if self.call_count >= len(self.responses):
164+
self.call_count = 0
165+
return response

0 commit comments

Comments
 (0)