mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-03-21 20:59:10 +00:00
github_secrets_info: new module (#11586)
* github_secrets_info: new module
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* clean tests
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* remove pynacl dep
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* fqcn
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* remove excess output
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* just return result as sample
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* only print secrets, adapt tests
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* Update plugins/modules/github_secrets_info.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/github_secrets_info.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/github_secrets_info.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* t is for typing, and typing is what we did
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* add info_module attributes
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
---------
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit df9b30448a)
This commit is contained in:
parent
7784fbdf17
commit
698e1ca203
3 changed files with 258 additions and 0 deletions
2
.github/BOTMETA.yml
vendored
2
.github/BOTMETA.yml
vendored
|
|
@ -644,6 +644,8 @@ files:
|
||||||
maintainers: atorrescogollo
|
maintainers: atorrescogollo
|
||||||
$modules/github_secrets.py:
|
$modules/github_secrets.py:
|
||||||
maintainers: konstruktoid
|
maintainers: konstruktoid
|
||||||
|
$modules/github_secrets_info.py:
|
||||||
|
maintainers: konstruktoid
|
||||||
$modules/gitlab_:
|
$modules/gitlab_:
|
||||||
keywords: gitlab source_control
|
keywords: gitlab source_control
|
||||||
maintainers: $team_gitlab
|
maintainers: $team_gitlab
|
||||||
|
|
|
||||||
158
plugins/modules/github_secrets_info.py
Normal file
158
plugins/modules/github_secrets_info.py
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
# Copyright (c) Ansible project
|
||||||
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
DOCUMENTATION = r"""
|
||||||
|
module: github_secrets_info
|
||||||
|
short_description: List GitHub repository or organization secrets
|
||||||
|
description:
|
||||||
|
- List secrets in a GitHub repository or organization.
|
||||||
|
author:
|
||||||
|
- Thomas Sjögren (@konstruktoid)
|
||||||
|
version_added: '12.5.0'
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.attributes
|
||||||
|
- community.general.attributes.info_module
|
||||||
|
options:
|
||||||
|
organization:
|
||||||
|
description:
|
||||||
|
- The GitHub username or organization name.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
aliases: ["org", "username"]
|
||||||
|
repository:
|
||||||
|
description:
|
||||||
|
- The name of the repository.
|
||||||
|
- If not provided, the listing will be at organization level.
|
||||||
|
type: str
|
||||||
|
aliases: ["repo"]
|
||||||
|
api_url:
|
||||||
|
description:
|
||||||
|
- The base URL for the GitHub API.
|
||||||
|
type: str
|
||||||
|
default: "https://api.github.com"
|
||||||
|
token:
|
||||||
|
description:
|
||||||
|
- The GitHub token used for authentication.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = r"""
|
||||||
|
- name: List Github secret
|
||||||
|
community.general.github_secrets_info:
|
||||||
|
token: "{{ lookup('ansible.builtin.env', 'GITHUB_TOKEN') }}"
|
||||||
|
repository: "ansible"
|
||||||
|
organization: "ansible"
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = r"""
|
||||||
|
secrets:
|
||||||
|
description: The list of currently existing secrets.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
returned: success
|
||||||
|
sample: [
|
||||||
|
{
|
||||||
|
"created_at": "2026-01-11T23:19:00Z",
|
||||||
|
"name": "ANSIBLE",
|
||||||
|
"updated_at": "2026-02-15T22:18:16Z"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description: The name of the secret.
|
||||||
|
type: str
|
||||||
|
created_at:
|
||||||
|
description: The date and time when the secret was created.
|
||||||
|
type: str
|
||||||
|
updated_at:
|
||||||
|
description: The date and time when the secret was last updated.
|
||||||
|
type: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import typing as t
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.urls import fetch_url
|
||||||
|
|
||||||
|
from ansible_collections.community.general.plugins.module_utils import deps
|
||||||
|
|
||||||
|
|
||||||
|
def list_secrets(
|
||||||
|
module: AnsibleModule,
|
||||||
|
api_url: str,
|
||||||
|
headers: dict[str, str],
|
||||||
|
organization: str,
|
||||||
|
repository: str,
|
||||||
|
) -> dict[str, list]:
|
||||||
|
url = (
|
||||||
|
f"{api_url}/repos/{organization}/{repository}/actions/secrets"
|
||||||
|
if repository
|
||||||
|
else f"{api_url}/orgs/{organization}/actions/secrets"
|
||||||
|
)
|
||||||
|
|
||||||
|
resp, info = fetch_url(module, url, headers=headers, method="GET")
|
||||||
|
|
||||||
|
if info["status"] == HTTPStatus.OK:
|
||||||
|
body = resp.read()
|
||||||
|
return {"secrets": json.loads(body).get("secrets", [])}
|
||||||
|
elif info["status"] == HTTPStatus.NOT_FOUND:
|
||||||
|
return {
|
||||||
|
"secrets": [],
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
module.fail_json(msg=f"Failed to list secrets: {info}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Ansible module entry point."""
|
||||||
|
argument_spec = {
|
||||||
|
"organization": {
|
||||||
|
"type": "str",
|
||||||
|
"aliases": ["org", "username"],
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"repository": {"type": "str", "aliases": ["repo"]},
|
||||||
|
"api_url": {"type": "str", "default": "https://api.github.com"},
|
||||||
|
"token": {"type": "str", "required": True, "no_log": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=argument_spec,
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
deps.validate(module)
|
||||||
|
|
||||||
|
organization: str = module.params["organization"]
|
||||||
|
repository: str = module.params["repository"]
|
||||||
|
api_url: str = module.params["api_url"]
|
||||||
|
token: str = module.params["token"]
|
||||||
|
|
||||||
|
result: dict[str, t.Any] = {}
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Accept": "application/vnd.github+json",
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets = list_secrets(module, api_url, headers, organization, repository)
|
||||||
|
|
||||||
|
result["changed"] = False
|
||||||
|
result.update(
|
||||||
|
secrets=secrets["secrets"],
|
||||||
|
)
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
98
tests/unit/plugins/modules/test_github_secrets_info.py
Normal file
98
tests/unit/plugins/modules/test_github_secrets_info.py
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
# Copyright (c) Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import (
|
||||||
|
AnsibleExitJson,
|
||||||
|
exit_json,
|
||||||
|
fail_json,
|
||||||
|
set_module_args,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.general.plugins.modules import github_secrets_info
|
||||||
|
|
||||||
|
GITHUB_SECRETS_RESPONSE = {
|
||||||
|
"total_count": 2,
|
||||||
|
"secrets": [
|
||||||
|
{
|
||||||
|
"name": "SECRET1",
|
||||||
|
"created_at": "2026-01-01T00:00:00Z",
|
||||||
|
"updated_at": "2026-01-02T00:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SECRET2",
|
||||||
|
"created_at": "2026-01-03T00:00:00Z",
|
||||||
|
"updated_at": "2026-01-04T00:00:00Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def make_fetch_url_response(body, status=200):
|
||||||
|
response = MagicMock()
|
||||||
|
response.read.return_value = json.dumps(body).encode("utf-8")
|
||||||
|
info = {"status": status, "msg": f"OK ({len(json.dumps(body))} bytes)"}
|
||||||
|
return (response, info)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def patch_module():
|
||||||
|
with patch.multiple(
|
||||||
|
"ansible.module_utils.basic.AnsibleModule",
|
||||||
|
exit_json=exit_json,
|
||||||
|
fail_json=fail_json,
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fetch_url_mock():
|
||||||
|
with patch.object(github_secrets_info, "fetch_url") as mock:
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_repo_secrets(fetch_url_mock):
|
||||||
|
fetch_url_mock.side_effect = [
|
||||||
|
make_fetch_url_response(GITHUB_SECRETS_RESPONSE),
|
||||||
|
make_fetch_url_response({}, status=200),
|
||||||
|
]
|
||||||
|
|
||||||
|
with set_module_args(
|
||||||
|
{
|
||||||
|
"organization": "myorg",
|
||||||
|
"repository": "myrepo",
|
||||||
|
"token": "ghp_test_token",
|
||||||
|
}
|
||||||
|
):
|
||||||
|
with pytest.raises(AnsibleExitJson) as exc:
|
||||||
|
github_secrets_info.main()
|
||||||
|
|
||||||
|
result = exc.value.args[0]
|
||||||
|
assert result["changed"] is False
|
||||||
|
assert result["secrets"] == GITHUB_SECRETS_RESPONSE["secrets"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_fail_list_repo_secrets(fetch_url_mock):
|
||||||
|
fetch_url_mock.side_effect = [
|
||||||
|
make_fetch_url_response({}, status=404),
|
||||||
|
]
|
||||||
|
|
||||||
|
with set_module_args(
|
||||||
|
{
|
||||||
|
"organization": "myorg",
|
||||||
|
"repository": "myrepo",
|
||||||
|
"token": "ghp_test_token",
|
||||||
|
}
|
||||||
|
):
|
||||||
|
with pytest.raises(AnsibleExitJson) as exc:
|
||||||
|
github_secrets_info.main()
|
||||||
|
|
||||||
|
result = exc.value.args[0]
|
||||||
|
assert result["changed"] is False
|
||||||
|
assert result["secrets"] == []
|
||||||
Loading…
Add table
Add a link
Reference in a new issue