Skip to content

Commit 88ca558

Browse files
fix(secret): add support of secret path
1 parent 21a7c95 commit 88ca558

5 files changed

Lines changed: 482 additions & 3 deletions

File tree

plugins/module_utils/model.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class Secret(Model):
7474
id: str = ""
7575
description: str = ""
7676
tags: list[str] = field(default_factory=list)
77+
path: str = ""
7778

7879

7980
@dataclass

plugins/module_utils/scaleway_secret.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,22 @@ def build_secret_version(parameters: dict) -> SecretVersion:
2323

2424
def get_secret(api: "SecretV1Beta1API", **kwargs) -> Secret:
2525
"""
26-
Get a secret by secret_id or name
26+
Get a secret by secret_id or name with optional path.
2727
"""
2828
if "secret_id" in kwargs:
2929
secret = api.get_secret(secret_id=kwargs["secret_id"])
3030

3131
elif "name" in kwargs:
32-
secrets = api.list_secrets(name=kwargs["name"], scheduled_for_deletion=False)
32+
list_kwargs = dict(name=kwargs["name"], scheduled_for_deletion=False)
33+
if "path" in kwargs:
34+
list_kwargs["path"] = kwargs["path"]
35+
secrets = api.list_secrets(**list_kwargs)
3336

3437
if len(secrets.secrets) == 0:
38+
if "path" in kwargs:
39+
raise SecretNotFound(
40+
f"Secret {kwargs['name']} not found at path {kwargs['path']}"
41+
)
3542
raise SecretNotFound(f"Secret {kwargs['name']} not found")
3643

3744
secret = secrets.secrets[0]
@@ -72,7 +79,10 @@ def update_secret(
7279
7380
return changed, local_model, remote_model
7481
"""
75-
remote_model = get_secret(api, name=parameters.get("name"))
82+
lookup = {"name": parameters.get("name")}
83+
if "path" in parameters:
84+
lookup["path"] = parameters["path"]
85+
remote_model = get_secret(api, **lookup)
7686

7787
# build and diff source model with the api one
7888
local_model = build_secret(parameters)

plugins/modules/scaleway_secret.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
description: name
3232
type: str
3333
required: true
34+
path:
35+
description: path
36+
type: str
37+
required: false
3438
project_id:
3539
description: project_id
3640
type: str
@@ -203,6 +207,7 @@ def main() -> None:
203207
dict(
204208
state=dict(type="str", default="present", choices=["absent", "present"]),
205209
name=dict(type="str", required=True),
210+
path=dict(type="str", required=False),
206211
project_id=dict(type="str", required=False),
207212
tags=dict(type="list", required=False, elements="str"),
208213
description=dict(type="str", required=False),

tests/unit/plugins/test_scaleway_secret.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,44 @@ def test_create_with_default_project_id(
9191
},
9292
)
9393

94+
@pytest.mark.parametrize(
95+
"set_module_args",
96+
[
97+
{
98+
"name": "test_secret",
99+
"path": "/custom/path",
100+
"tags": ["test", "secret"],
101+
"description": "test_description",
102+
"protected": False,
103+
}
104+
],
105+
indirect=True,
106+
)
107+
@patch.object(secret_api, "unmarshal_Secret")
108+
@patch.object(secret_api.SecretV1Beta1API, "_request")
109+
def test_create_with_custom_path(
110+
self,
111+
mock_request,
112+
mock_unmarshal_secret,
113+
scaleway_config_profile,
114+
set_module_args,
115+
):
116+
mock_unmarshal_secret.return_value = MagicMock()
117+
mock_request.return_value = MagicMock(status_code=201)
118+
scaleway_secret.main()
119+
mock_request.assert_called_once_with(
120+
"POST",
121+
f"/secret-manager/v1beta1/regions/{scaleway_config_profile.default_region}/secrets",
122+
body={
123+
"name": "test_secret",
124+
"path": "/custom/path",
125+
"tags": ["test", "secret"],
126+
"description": "test_description",
127+
"protected": False,
128+
"project_id": scaleway_config_profile.default_project_id,
129+
},
130+
)
131+
94132
@pytest.mark.parametrize(
95133
"set_module_args",
96134
[
@@ -130,6 +168,154 @@ def __dict__(self):
130168
f"/secret-manager/v1beta1/regions/{scaleway_config_profile.default_region}/secrets/{self.test_uuid}",
131169
)
132170

171+
@pytest.mark.parametrize(
172+
"set_module_args",
173+
[
174+
{
175+
"name": "test_secret",
176+
"path": "/custom/path",
177+
"state": "absent",
178+
}
179+
],
180+
indirect=True,
181+
)
182+
@patch.object(secret_api, "unmarshal_ListSecretsResponse")
183+
@patch.object(secret_api.SecretV1Beta1API, "_request")
184+
def test_delete_secret_with_path(
185+
self,
186+
mock_request,
187+
mock_unmarshal_list_secrets_response,
188+
scaleway_config_profile,
189+
set_module_args,
190+
):
191+
class MockedSecret(MagicMock):
192+
id = self.test_uuid
193+
name = "test_secret"
194+
path = "/custom/path"
195+
196+
def __dict__(self):
197+
return {
198+
"id": self.id,
199+
"name": self.name,
200+
"path": self.path,
201+
}
202+
203+
mock_unmarshal_list_secrets_response.return_value = MagicMock(
204+
secrets=[MockedSecret]
205+
)
206+
mock_request.side_effect = [
207+
MagicMock(status_code=200), # list secret response
208+
MagicMock(status_code=204), # delete secret response
209+
]
210+
scaleway_secret.main()
211+
mock_request.assert_any_call(
212+
"DELETE",
213+
f"/secret-manager/v1beta1/regions/{scaleway_config_profile.default_region}/secrets/{self.test_uuid}",
214+
)
215+
216+
def test_update_secret_with_path_check_mode(self):
217+
"""Test updating a secret with path in check mode"""
218+
from ....plugins.module_utils.scaleway_secret import update_secret
219+
from ....plugins.module_utils.model import Secret
220+
221+
mock_api = MagicMock()
222+
223+
existing_secret = MagicMock()
224+
existing_secret.name = "test_secret"
225+
existing_secret.path = "/custom/path"
226+
existing_secret.id = self.test_uuid
227+
existing_secret.description = "old_description"
228+
existing_secret.tags = ["old-tag"]
229+
230+
mock_list_response = MagicMock()
231+
mock_list_response.secrets = [existing_secret]
232+
233+
mock_api.list_secrets.return_value = mock_list_response
234+
235+
parameters = {
236+
"name": "test_secret",
237+
"path": "/custom/path",
238+
"description": "test_description",
239+
"tags": ["test", "secret"],
240+
"protected": False,
241+
}
242+
243+
changed, local_model, remote_model = update_secret(
244+
mock_api, parameters, check_mode=True
245+
)
246+
247+
mock_api.list_secrets.assert_called_once_with(
248+
name="test_secret", scheduled_for_deletion=False, path="/custom/path"
249+
)
250+
251+
mock_api.update_secret.assert_not_called()
252+
253+
assert changed is False
254+
assert local_model.name == "test_secret"
255+
assert local_model.path == "/custom/path"
256+
assert local_model.description == "test_description"
257+
assert isinstance(local_model, Secret)
258+
259+
def test_update_secret_with_path(self):
260+
"""Test updating a secret with path parameter"""
261+
from ....plugins.module_utils.scaleway_secret import update_secret
262+
from ....plugins.module_utils.model import Secret
263+
264+
mock_api = MagicMock()
265+
266+
existing_secret = MagicMock()
267+
existing_secret.name = "test_secret"
268+
existing_secret.path = "/custom/path"
269+
existing_secret.id = self.test_uuid
270+
existing_secret.description = "old_description"
271+
existing_secret.tags = ["old-tag"]
272+
273+
mock_list_response = MagicMock()
274+
mock_list_response.secrets = [existing_secret]
275+
276+
updated_secret = MagicMock()
277+
updated_secret.name = "test_secret"
278+
updated_secret.path = "/custom/path"
279+
updated_secret.id = self.test_uuid
280+
updated_secret.description = "test_description"
281+
updated_secret.tags = ["test", "secret"]
282+
283+
mock_api.list_secrets.return_value = mock_list_response
284+
mock_api.update_secret.return_value = updated_secret
285+
286+
parameters = {
287+
"name": "test_secret",
288+
"path": "/custom/path",
289+
"description": "test_description",
290+
"tags": ["test", "secret"],
291+
"protected": False,
292+
}
293+
294+
changed, local_model, remote_model = update_secret(mock_api, parameters)
295+
296+
mock_api.list_secrets.assert_called_once_with(
297+
name="test_secret", scheduled_for_deletion=False, path="/custom/path"
298+
)
299+
300+
mock_api.update_secret.assert_called_once_with(
301+
secret_id=self.test_uuid,
302+
description="test_description",
303+
tags=["test", "secret"],
304+
)
305+
306+
assert changed is True
307+
assert local_model.name == "test_secret"
308+
assert local_model.path == "/custom/path"
309+
assert local_model.description == "test_description"
310+
assert local_model.tags == ["test", "secret"]
311+
assert isinstance(local_model, Secret)
312+
313+
assert remote_model.name == "test_secret"
314+
assert remote_model.path == "/custom/path"
315+
assert remote_model.description == "old_description"
316+
assert remote_model.tags == ["old-tag"]
317+
assert isinstance(remote_model, Secret)
318+
133319

134320
@patch.object(basic.AnsibleModule, "exit_json", MagicMock())
135321
class TestScalewaySecretVersion:

0 commit comments

Comments
 (0)