mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-03-22 05:09:12 +00:00
github_secrets: new module (#11514)
* add support for managing GitHub secrets
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* fix tab
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* update for sanity
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* more sanity fixes
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* update botmeta
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* formating
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* remove list function
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* remove docstring, format text strings and return codes
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* switch to deps
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* black and ruff doesnt get along
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* initial unit tests
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* update non-existing secret test
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* update description and details
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* handle when a secret cant be deleted
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* fail if not acceptable error codes
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* add test for non-acceptable status codes
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* remove local ruff config
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* allow empty strings
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* set required_
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* extend tests
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* cleanup
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* cover all, got a git urlopen error
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* cover all, got a git urlopen error
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* ensure value cant be None
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* check_mode
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* bump to 12.5.0
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* Update plugins/modules/github_secrets.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* extend check_mode and related tests
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* split constants and return dict when checking secret
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* switch to HTTPStatus
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* replace DELETE and UPDATE with NO_CONTENT
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* Update plugins/modules/github_secrets.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/github_secrets.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* update tests
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
* Update plugins/modules/github_secrets.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/github_secrets.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/github_secrets.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/github_secrets.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/github_secrets.py
Co-authored-by: Felix Fontein <felix@fontein.de>
---------
Signed-off-by: Thomas Sjögren <konstruktoid@users.noreply.github.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 46ffec6f0e)
This commit is contained in:
parent
86616b1559
commit
739b5ac6ff
4 changed files with 764 additions and 0 deletions
360
tests/unit/plugins/modules/test_github_secrets.py
Normal file
360
tests/unit/plugins/modules/test_github_secrets.py
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
# 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,
|
||||
AnsibleFailJson,
|
||||
exit_json,
|
||||
fail_json,
|
||||
set_module_args,
|
||||
)
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import github_secrets
|
||||
|
||||
PUBLIC_KEY_PAYLOAD = {
|
||||
"key_id": "100",
|
||||
"key": "j6gSA9mu5bO8ig+YBNU6oXRhGwd3Z4EFiS9rVmU9gwo=",
|
||||
}
|
||||
|
||||
|
||||
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, "fetch_url") as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
def test_encrypt_secret():
|
||||
result = github_secrets.encrypt_secret(PUBLIC_KEY_PAYLOAD["key"], "ansible")
|
||||
assert isinstance(result, str)
|
||||
assert result != "ansible"
|
||||
assert len(result) > 70
|
||||
|
||||
|
||||
def test_fail_without_parameters():
|
||||
with pytest.raises(AnsibleFailJson):
|
||||
with set_module_args({}):
|
||||
github_secrets.main()
|
||||
|
||||
|
||||
def test_fail_present_without_value():
|
||||
with pytest.raises(AnsibleFailJson) as exc:
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"value": None,
|
||||
"state": "present",
|
||||
"token": "ghp_test_token",
|
||||
}
|
||||
):
|
||||
github_secrets.main()
|
||||
assert "value' parameter cannot be empty" in exc.value.args[0]["details"]
|
||||
|
||||
|
||||
def test_fail_org_secret_present_without_visibility():
|
||||
with pytest.raises(AnsibleFailJson) as exc:
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"key": "ORG_SECRET",
|
||||
"value": "org_value",
|
||||
"state": "present",
|
||||
"token": "ghp_test_token",
|
||||
}
|
||||
):
|
||||
github_secrets.main()
|
||||
assert "'visibility' must be provided" in exc.value.args[0]["details"]
|
||||
|
||||
|
||||
def test_create_repo_secret(fetch_url_mock):
|
||||
fetch_url_mock.side_effect = [
|
||||
make_fetch_url_response(PUBLIC_KEY_PAYLOAD),
|
||||
make_fetch_url_response({}, status=201),
|
||||
]
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"value": "secret_value",
|
||||
"state": "present",
|
||||
"token": "ghp_test_token",
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleExitJson) as exc:
|
||||
github_secrets.main()
|
||||
|
||||
result = exc.value.args[0]
|
||||
assert result["changed"] is True
|
||||
assert result["result"]["status"] == 201
|
||||
assert result["result"]["response"] == "Secret created"
|
||||
|
||||
|
||||
def test_update_repo_secret(fetch_url_mock):
|
||||
fetch_url_mock.side_effect = [
|
||||
make_fetch_url_response(PUBLIC_KEY_PAYLOAD),
|
||||
make_fetch_url_response({}, status=204),
|
||||
]
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"value": "new_value",
|
||||
"state": "present",
|
||||
"token": "ghp_test_token",
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleExitJson) as exc:
|
||||
github_secrets.main()
|
||||
|
||||
result = exc.value.args[0]
|
||||
assert result["changed"] is True
|
||||
assert result["result"]["response"] == "Secret updated"
|
||||
|
||||
|
||||
def test_create_repo_secret_check_mode(fetch_url_mock):
|
||||
fetch_url_mock.side_effect = [
|
||||
make_fetch_url_response(PUBLIC_KEY_PAYLOAD),
|
||||
make_fetch_url_response({}, status=404),
|
||||
]
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"value": "new_value",
|
||||
"state": "present",
|
||||
"token": "ghp_test_token",
|
||||
"_ansible_check_mode": True,
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleExitJson) as exc:
|
||||
github_secrets.main()
|
||||
|
||||
result = exc.value.args[0]
|
||||
assert result["changed"] is True
|
||||
assert result["result"]["status"] == 201
|
||||
assert result["result"]["response"] == "Secret created"
|
||||
|
||||
|
||||
def test_update_repo_secret_check_mode(fetch_url_mock):
|
||||
fetch_url_mock.side_effect = [
|
||||
make_fetch_url_response(PUBLIC_KEY_PAYLOAD),
|
||||
make_fetch_url_response({}, status=200),
|
||||
]
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"value": "new_value",
|
||||
"state": "present",
|
||||
"token": "ghp_test_token",
|
||||
"_ansible_check_mode": True,
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleExitJson) as exc:
|
||||
github_secrets.main()
|
||||
|
||||
result = exc.value.args[0]
|
||||
assert result["changed"] is True
|
||||
assert result["result"]["status"] == 204
|
||||
assert result["result"]["response"] == "Secret updated"
|
||||
|
||||
|
||||
def test_delete_repo_secret_check_mode(fetch_url_mock):
|
||||
fetch_url_mock.side_effect = [
|
||||
make_fetch_url_response(PUBLIC_KEY_PAYLOAD),
|
||||
make_fetch_url_response({}, status=200),
|
||||
]
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"state": "absent",
|
||||
"token": "ghp_test_token",
|
||||
"_ansible_check_mode": True,
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleExitJson) as exc:
|
||||
github_secrets.main()
|
||||
|
||||
result = exc.value.args[0]
|
||||
assert result["changed"] is True
|
||||
assert result["result"]["status"] == 204
|
||||
assert result["result"]["response"] == "Secret deleted"
|
||||
|
||||
|
||||
def test_update_empty_repo_secret(fetch_url_mock):
|
||||
fetch_url_mock.side_effect = [
|
||||
make_fetch_url_response(PUBLIC_KEY_PAYLOAD),
|
||||
make_fetch_url_response({}, status=204),
|
||||
]
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"value": "",
|
||||
"state": "present",
|
||||
"token": "ghp_test_token",
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleExitJson) as exc:
|
||||
github_secrets.main()
|
||||
|
||||
result = exc.value.args[0]
|
||||
assert result["changed"] is True
|
||||
assert result["result"]["response"] == "Secret updated"
|
||||
|
||||
|
||||
def test_update_missing_value_repo_secret(fetch_url_mock):
|
||||
fetch_url_mock.side_effect = [
|
||||
make_fetch_url_response(PUBLIC_KEY_PAYLOAD),
|
||||
make_fetch_url_response({}, status=204),
|
||||
]
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"state": "present",
|
||||
"token": "ghp_test_token",
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleFailJson) as exc:
|
||||
github_secrets.main()
|
||||
|
||||
assert "the following are missing: value" in exc.value.args[0]["msg"]
|
||||
|
||||
|
||||
def test_delete_repo_secret(fetch_url_mock):
|
||||
fetch_url_mock.return_value = make_fetch_url_response({}, status=204)
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"state": "absent",
|
||||
"token": "ghp_test_token",
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleExitJson) as exc:
|
||||
github_secrets.main()
|
||||
|
||||
result = exc.value.args[0]
|
||||
assert result["changed"] is True
|
||||
assert result["result"]["status"] == 204
|
||||
assert result["result"]["response"] == "Secret deleted"
|
||||
|
||||
|
||||
def test_delete_dne_repo_secret(fetch_url_mock):
|
||||
fetch_url_mock.return_value = make_fetch_url_response({}, status=404)
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "DOES_NOT_EXIST",
|
||||
"state": "absent",
|
||||
"token": "ghp_test_token",
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleExitJson) as exc:
|
||||
github_secrets.main()
|
||||
|
||||
result = exc.value.args[0]
|
||||
assert result["changed"] is False
|
||||
assert result["result"]["status"] == 404
|
||||
assert result["result"]["response"] == "Secret not found"
|
||||
|
||||
|
||||
def test_fail_get_public_key(fetch_url_mock):
|
||||
fetch_url_mock.return_value = make_fetch_url_response({}, status=403)
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"value": "secret_value",
|
||||
"state": "present",
|
||||
"token": "ghp_test_token",
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleFailJson) as exc:
|
||||
github_secrets.main()
|
||||
assert "Failed to get public key" in exc.value.args[0]["msg"]
|
||||
|
||||
|
||||
def test_fail_upsert_secret(fetch_url_mock):
|
||||
fetch_url_mock.side_effect = [
|
||||
make_fetch_url_response(PUBLIC_KEY_PAYLOAD),
|
||||
make_fetch_url_response({}, status=422),
|
||||
]
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"value": "secret_value",
|
||||
"state": "present",
|
||||
"token": "ghp_test_token",
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleFailJson) as exc:
|
||||
github_secrets.main()
|
||||
assert "Failed to upsert secret" in exc.value.args[0]["msg"]
|
||||
|
||||
|
||||
def test_fail_delete_secret(fetch_url_mock):
|
||||
fetch_url_mock.return_value = make_fetch_url_response({}, status=503)
|
||||
|
||||
with set_module_args(
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "myrepo",
|
||||
"key": "MY_SECRET",
|
||||
"state": "absent",
|
||||
"token": "ghp_test_token",
|
||||
}
|
||||
):
|
||||
with pytest.raises(AnsibleFailJson) as exc:
|
||||
github_secrets.main()
|
||||
assert "Failed to delete secret" in exc.value.args[0]["msg"]
|
||||
Loading…
Add table
Add a link
Reference in a new issue