Compare commits

...

15 Commits

Author SHA1 Message Date
ab996acb36
test: remove unused tests
All checks were successful
pull-requests-open / Check commit compliance (pull_request) Successful in 6s
pull-requests-open / Run tests (pull_request) Successful in 8s
2024-09-15 15:38:00 +02:00
2df6309bf1
ci: some test path
All checks were successful
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Successful in 7s
2024-09-14 21:41:43 +02:00
83d275f313
ci: some test path
Some checks failed
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Failing after 4s
2024-09-14 21:40:36 +02:00
b7e65abd3d
ci: replace requirement file for unit tests
Some checks failed
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Failing after 29s
2024-09-14 21:37:40 +02:00
9ed53d92a4
ci: more testing CI
Some checks failed
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Failing after 15s
2024-09-14 21:36:28 +02:00
8bcc020346
ci: test with failing tests
Some checks failed
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Failing after 48s
2024-09-14 21:16:00 +02:00
a0e45be691
ci: test ansible-test action
All checks were successful
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Successful in 1m50s
2024-09-14 21:09:31 +02:00
29675007db
ci: install collection locally to run tests
Some checks failed
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Failing after 26s
2024-09-14 20:53:19 +02:00
74c9f8b2f8
ci: fix working dir for running tests
Some checks failed
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Failing after 26s
2024-09-14 20:49:33 +02:00
883140deea
ci: fix working dir for running tests
Some checks failed
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Failing after 26s
2024-09-14 20:48:21 +02:00
194d5ed1f0
ci: fix tests
Some checks failed
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Failing after 32s
2024-09-14 20:47:50 +02:00
84989cfbee
ci: make a simple test setup for now
Some checks failed
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Failing after 26s
2024-09-14 13:18:01 +02:00
452a1cd252
ci: add dependencies between PR jobs
Some checks failed
pull-requests-open / Check commit compliance (pull_request) Successful in 5s
pull-requests-open / Run tests (pull_request) Failing after 1m18s
2024-09-14 13:15:07 +02:00
f5fc5bb845
feat: add unit tests for public_cloud_config_info module
Some checks failed
pull-requests-open / Check commit compliance (pull_request) Successful in 38s
pull-requests-open / Run tests (pull_request) Failing after 1m27s
2024-09-14 13:13:43 +02:00
cd14bbc629
feat: add public_cloud_config_info module 2024-09-12 23:17:36 +02:00
16 changed files with 592 additions and 13 deletions

View File

@ -0,0 +1,67 @@
---
name: pull-requests-open
on:
pull_request:
types:
- opened
- edited
- synchronize
branches:
- main
- develop
jobs:
commit-history-check:
name: Check commit compliance
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Install commitizen
run: pip3 install commitizen
shell: bash
working-directory: ${{ gitea.workspace }}
- name: Verify commit message compliance
run: |
echo "cz check --rev-range origin/${{ gitea.event.pull_request.base.ref }}.."
cz check --rev-range origin/${{ gitea.event.pull_request.base.ref }}..
shell: bash
working-directory: ${{ gitea.workspace }}
run-tests:
name: Run tests
runs-on: ubuntu-latest
needs: commit-history-check
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install requirements
run: pip3 install -r tests/requirements.txt
shell: bash
working-directory: ${{ gitea.workspace }}
- name: Setup testing environment
run: ansible-galaxy collection install ${{ gitea.workspace }}
shell: bash
working-directory: ${{ gitea.workspace }}
- name: Run ansible unit tests
run: ansible-test units --coverage
shell: bash
working-directory: /root/.ansible/collections/ansible_collections/ednz_cloud/infomaniak
- name: Print coverage informations
run: ansible-test coverage report
shell: bash
working-directory: /root/.ansible/collections/ansible_collections/ednz_cloud/infomaniak

View File

@ -0,0 +1,54 @@
---
name: release
on:
push:
branches:
- main
jobs:
do-release:
if: "!startsWith(github.event.head_commit.message, 'bump:')"
runs-on: ubuntu-latest
name: Bump version and create changelog with commitizen
steps:
- name: Get secrets from vault
id: import-secrets
uses: hashicorp/vault-action@v3
with:
url: "https://vault.ednz.fr"
method: approle
roleId: ${{ secrets.VAULT_APPROLE_ID }}
secretId: ${{ secrets.VAULT_APPROLE_SECRET_ID }}
secrets: |
kv/data/applications/gitea/users/actions username | GITEA_ACTIONS_USERNAME ;
kv/data/applications/gitea/users/actions token_write | GITEA_ACTIONS_TOKEN ;
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.import-secrets.outputs.GITEA_ACTIONS_TOKEN }}
- name: Install commitizen
run: pip3 install commitizen
shell: bash
working-directory: ${{ gitea.workspace }}
- name: Configure git credentials
uses: oleksiyrudenko/gha-git-credentials@v2
with:
global: true
name: "Gitea-Actions Bot"
email: "gitea-actions@ednz.fr"
actor: ${{ steps.import-secrets.outputs.GITEA_ACTIONS_USERNAME }}
token: ${{ steps.import-secrets.outputs.GITEA_ACTIONS_TOKEN }}
- name: Do release
run: cz -nr 21 bump --yes
shell: bash
working-directory: ${{ gitea.workspace }}
- name: Push release
run: git push && git push --tags
shell: bash
working-directory: ${{ gitea.workspace }}

19
.gitignore vendored
View File

@ -1,11 +1,14 @@
# ignore molecule/testinfra pycache
**/__pycache__
# ignore vscode files
.vscode
roles/ednz_cloud.*
vault_config.yml
consul_config.yml
**/certificates/**
**/secrets/credentials.yml
**/secrets/credentials.decrypt.yml
**/secrets/vault.yml
**/.ansible-vault
# ignore test output (auto-generated)
tests/output/**
# ignore virtual environments
.env
.venv
env
venv

View File

@ -1,7 +1,7 @@
---
namespace: ednz_cloud
name: hashistack
version: 0.7.0
name: infomaniak
version: 0.0.0
readme: README.md
authors:
- Bertrand Lanson <bertrand.lanson@protonmail.com>
@ -11,9 +11,9 @@ license_file: "LICENSE"
# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character
# requirements as 'namespace' and 'name'
tags: ["tools"]
tags: ["infomaniak", "cloud"]
dependencies: {}
repository: https://git.ednz.fr/ansible-collections/hashistack
repository: https://git.ednz.fr/ansible-collections/infomaniak
documentation: http://docs.example.com
homepage: http://example.com
issues: http://example.com/issue/tracker
@ -22,7 +22,6 @@ issues: http://example.com/issue/tracker
# uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry',
# and '.git' are always filtered. Mutually exclusive with 'manifest'
build_ignore:
- assets**
- .gitea**
# A dict controlling use of manifest directives used in building the collection artifact. The key 'directives' is a
# list of MANIFEST.in style

View File

View File

@ -0,0 +1,14 @@
---
- name: Converge
hosts: all
become: true
tasks:
- name: "Run module"
ednz_cloud.infomaniak.public_cloud_config_info:
api_token: "{{ lookup('ansible.builtin.env', 'IK_API_TOKEN') }}"
account_id: "{{ lookup('ansible.builtin.env', 'IK_ACCOUNT_ID') }}"
register: _result
- name: "Print result"
ansible.builtin.debug:
msg: "{{ _result }}"

View File

@ -0,0 +1,37 @@
---
dependency:
name: galaxy
options:
requirements-file: ./requirements.yml
driver:
name: docker
platforms:
- name: instance
image: geerlingguy/docker-${MOLECULE_TEST_OS}-ansible
command: ""
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup
cgroupns_mode: host
privileged: true
pre_build_image: true
provisioner:
name: ansible
config_options:
defaults:
remote_tmp: /tmp/.ansible
verifier:
name: ansible
scenario:
name: public_cloud_config_info
test_sequence:
- dependency
- cleanup
- destroy
- syntax
- create
- prepare
- converge
- idempotence
- verify
- cleanup
- destroy

View File

@ -0,0 +1,9 @@
---
- name: Converge
hosts: all
become: true
tasks:
- name: "Install python requirements"
ansible.builtin.pip:
name: requests
executable: pip3

View File

@ -0,0 +1,4 @@
---
# requirements file for molecule
collections:
- name: ednz_cloud.infomaniak

View File

31
plugins/README.md Normal file
View File

@ -0,0 +1,31 @@
# Collections Plugins Directory
This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that
is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that
would contain module utils and modules respectively.
Here is an example directory of the majority of plugins currently supported by Ansible:
```
└── plugins
├── action
├── become
├── cache
├── callback
├── cliconf
├── connection
├── filter
├── httpapi
├── inventory
├── lookup
├── module_utils
├── modules
├── netconf
├── shell
├── strategy
├── terminal
├── test
└── vars
```
A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible-core/2.15/plugins/plugins.html).

View File

@ -0,0 +1,52 @@
import requests
import json
INFOMANIAK_API_URL = "https://api.infomaniak.com"
class InfomaniakAPIClient:
def __init__(self, api_version, api_token):
self.base_url = f"{INFOMANIAK_API_URL}/{api_version}"
self.headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
def _request(self, method, endpoint, data=None, params=None):
url = f"{self.base_url}{endpoint}"
try:
response = requests.request(
method=method,
url=url,
headers=self.headers,
json=data,
params=params,
timeout=10,
)
response.raise_for_status()
try:
return response.json()
except json.JSONDecodeError:
raise Exception(f"Invalid JSON response: {response.text}")
except requests.exceptions.HTTPError as http_err:
error_content = response.content if response else "No response"
raise Exception(
f"HTTP error occurred: {http_err}, Response content: {error_content}"
)
except requests.exceptions.RequestException as req_err:
raise Exception(f"Request error occurred: {req_err}")
except Exception as err:
raise Exception(f"An error occurred: {err}")
def get(self, endpoint, params=None):
return self._request("GET", endpoint, params=params)
def post(self, endpoint, data=None):
return self._request("POST", endpoint, data=data)
def put(self, endpoint, data=None):
return self._request("PUT", endpoint, data=data)
def delete(self, endpoint):
return self._request("DELETE", endpoint)

View File

@ -0,0 +1,110 @@
#!/usr/bin/python
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
---
module: ednz_cloud.infomaniak.public_cloud_config_info
short_description: Retrieves the public cloud configuration from the Infomaniak API.
version_added: "0.1.0"
description:
- This module queries the Infomaniak API to retrieve the public cloud configuration for a given account.
- It uses the provided API token for authentication and the account ID as a query parameter.
requirements:
- C(requests)
options:
api_token:
description: The API token used to authenticate with the Infomaniak API.
required: true
type: str
account_id:
description: The account ID for which to query the public cloud configuration.
required: true
type: str
author:
- Bertrand Lanson (@ednxzu)
"""
EXAMPLES = r"""
# Example: Retrieve public cloud configuration for account ID 965060
- name: Get public cloud configuration
public_cloud_config_info:
api_token: "your_api_token"
account_id: "123456"
"""
RETURN = r"""
config:
description:
- The public cloud configuration for the specified account.
type: dict
returned: always
sample:
free_tier: 300
free_tier_used: 24.34
account_resource_level: 2
valid_from: 1707584356
valid_to: 1717192799
project_count: 2
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ednz_cloud.infomaniak.plugins.module_utils.infomaniak_api_client import (
InfomaniakAPIClient,
)
def get_public_cloud_config(client: InfomaniakAPIClient, account_id: str):
endpoint = "/public_clouds/config"
params = {"account_id": account_id}
response = client.get(endpoint, params=params)
if response.get("result") != "success":
raise Exception(f"API request failed with result: {response.get('result')}")
return response.get("data", {})
def run_module():
module_args = dict(
api_token=dict(type="str", required=True, no_log=True),
account_id=dict(type="str", required=True),
)
result = dict(changed=False, config={})
module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)
api_token = module.params["api_token"]
account_id = module.params["account_id"]
if module.check_mode:
module.exit_json(
changed=False,
msg="Check mode: No changes made, would retrieve public cloud config.",
)
client = InfomaniakAPIClient(api_version="1", api_token=api_token)
try:
config_data = get_public_cloud_config(client, account_id)
result["config"] = config_data
module.exit_json(**result)
except Exception as e:
module.fail_json(msg=str(e))
def main():
run_module()
if __name__ == "__main__":
main()

5
tests/requirements.txt Normal file
View File

@ -0,0 +1,5 @@
ansible==10.3.0
requests==2.32.3
pytest==8.3.3
pytest-xdist==3.6.1
coverage==7.3.2

View File

@ -0,0 +1,194 @@
import unittest
import pytest
import json
from unittest.mock import patch, MagicMock, Mock
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils import basic
from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.ednz_cloud.infomaniak.plugins.modules import (
public_cloud_config_info,
)
def set_module_args(args):
if "_ansible_remote_tmp" not in args:
args["_ansible_remote_tmp"] = "/tmp"
if "_ansible_keep_remote_files" not in args:
args["_ansible_keep_remote_files"] = False
args = json.dumps({"ANSIBLE_MODULE_ARGS": args})
basic._ANSIBLE_ARGS = to_bytes(args)
class AnsibleExitJson(Exception):
def __init__(self, kwargs):
self.kwargs = kwargs
def exit_json(*args, **kwargs):
if "changed" not in kwargs:
kwargs["changed"] = False
raise AnsibleExitJson(kwargs)
def fail_json(*args, **kwargs):
raise SystemExit(json.dumps(kwargs))
class TestPublicCloudConfigInfoModule(unittest.TestCase):
"""
Unit tests for the public_cloud_config_info Ansible module.
"""
@patch(
"ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.AnsibleModule"
)
@patch(
"ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient"
)
def test_successful_retrieval(self, mock_client_class, mock_ansible_module):
"""
Test a successful retrieval of public cloud configuration.
"""
api_response_data = {
"result": "success",
"data": {
"free_tier": 300,
"free_tier_used": 24.34,
"account_resource_level": 2,
"valid_from": 1707584356,
"valid_to": 1717192799,
"project_count": 2,
},
}
mock_client_instance = MagicMock()
mock_client_instance.get.return_value = api_response_data
mock_client_class.return_value = mock_client_instance
mock_module_instance = MagicMock()
mock_module_instance.params = {
"api_token": "mock_api_token",
"account_id": "mock_account_id",
}
mock_module_instance.check_mode = False
mock_ansible_module.return_value = mock_module_instance
mock_module_instance.exit_json = MagicMock()
public_cloud_config_info.run_module()
mock_client_class.assert_called_with(
api_version="1", api_token="mock_api_token"
)
mock_client_instance.get.assert_called_once_with(
"/public_clouds/config", params={"account_id": "mock_account_id"}
)
mock_module_instance.exit_json.assert_called_once_with(
changed=False, config=api_response_data["data"]
)
def test_missing_required_params(self):
"""
Test behavior when required parameters are missing.
"""
set_module_args(
{
"api_token": "mock_api_token",
# 'account_id' is missing
}
)
with patch.object(AnsibleModule, "fail_json", side_effect=fail_json):
with pytest.raises(SystemExit) as e:
public_cloud_config_info.main()
captured_output = json.loads(e.value.args[0])
assert "msg" in captured_output
assert "missing required arguments: account_id" in captured_output["msg"]
def test_check_mode(self):
"""
Test the behavior in check mode, where no changes should be made.
"""
set_module_args(
{
"api_token": "mock_api_token",
"account_id": "mock_account_id",
"_ansible_check_mode": True,
}
)
with patch.object(
AnsibleModule, "exit_json", side_effect=exit_json
) as mock_exit_json:
with pytest.raises(AnsibleExitJson) as e:
public_cloud_config_info.main()
mock_exit_json.assert_called_once_with(
changed=False,
msg="Check mode: No changes made, would retrieve public cloud config.",
)
@patch(
"ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient"
)
def test_network_timeout(self, mock_client_class):
"""
Test behavior when a network timeout occurs.
"""
# Simulate a network timeout error
mock_client_instance = MagicMock()
mock_client_instance.get.side_effect = Exception("Request timeout")
mock_client_class.return_value = mock_client_instance
set_module_args(
{
"api_token": "mock_api_token",
"account_id": "mock_account_id",
}
)
with patch.object(
AnsibleModule, "fail_json", side_effect=fail_json
) as mock_fail_json:
with pytest.raises(SystemExit) as e:
public_cloud_config_info.main()
captured_output = json.loads(e.value.args[0])
assert "msg" in captured_output
assert captured_output["msg"] == "Request timeout"
mock_fail_json.assert_called_once()
@patch(
"ansible_collections.ednz_cloud.infomaniak.plugins.modules.public_cloud_config_info.InfomaniakAPIClient"
)
def test_invalid_json_response(self, mock_client_class):
"""
Test behavior when the API returns an invalid JSON response.
"""
mock_client_instance = MagicMock()
mock_client_instance.get.side_effect = Exception("Invalid JSON response")
mock_client_class.return_value = mock_client_instance
# Set module args with the required parameters
set_module_args(
{
"api_token": "mock_api_token",
"account_id": "mock_account_id",
}
)
with patch.object(
AnsibleModule, "fail_json", side_effect=fail_json
) as mock_fail_json:
with pytest.raises(SystemExit) as e:
public_cloud_config_info.main()
captured_output = json.loads(e.value.args[0])
assert "msg" in captured_output
assert captured_output["msg"] == "Invalid JSON response"
mock_fail_json.assert_called_once()