Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,31 @@ jobs:
integration-test:
environment: test
runs-on: ubuntu-latest
defaults:
run:
working-directory: ansible/ansible_collections/devolutions/dvls

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
path: ansible/ansible_collections/devolutions/dvls

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.x
python-version: 3.13

- name: Install Python dependencies
run: |
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txt
python3 -m pip list

- name: Build
- name: Test code sanity
run: ansible-test sanity

- name: Build collection
id: build
run: |
OUTPUT=$(ansible-galaxy collection build)
Expand All @@ -37,11 +45,30 @@ jobs:

- name: Install collection
run: ansible-galaxy collection install ${{ steps.build.outputs.collection_path }} --force
working-directory: tests/integration
working-directory: ansible/ansible_collections/devolutions/dvls/tests/integration

- name: Test collection documentation
run: |
# Generate the list of modules dynamically
ansible_modules=($(ansible-doc -l devolutions.dvls | awk '{print $1}'))

# Check if any modules were found
if [ ${#ansible_modules[@]} -eq 0 ]; then
echo "Error: No modules found for devolutions.dvls."
exit 1
fi
echo "Modules: ${ansible_modules[@]}"

# Loop through each module and run ansible-doc
for ansible_module in "${ansible_modules[@]}"; do
echo "Running ansible-doc for module: $ansible_module"
ansible-doc -t module "$ansible_module"
echo "----------------------------------------------------"
done

- name: Run get-vaults
run: ansible-playbook test_manage_server.yml
working-directory: tests/integration
working-directory: ansible/ansible_collections/devolutions/dvls/tests/integration
env:
DVLS_APP_KEY: ${{ secrets.DVLS_APP_KEY }}
DVLS_APP_SECRET: ${{ secrets.DVLS_APP_SECRET }}
Expand All @@ -50,7 +77,7 @@ jobs:

- name: Run get-secrets
run: ansible-playbook test_manage_secrets.yml
working-directory: tests/integration
working-directory: ansible/ansible_collections/devolutions/dvls/tests/integration
env:
DVLS_APP_KEY: ${{ secrets.DVLS_APP_KEY }}
DVLS_APP_SECRET: ${{ secrets.DVLS_APP_SECRET }}
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
.lock
.ansible

# asnible-test
output

# vscode..
.vscode

# macosx...
.DS_Store

# python envs
# python
*.pyc
.env
.venv
env
Expand Down
1 change: 1 addition & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ignore = ["E402"]
19 changes: 9 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This Ansible module allows you to authenticate with DVLS and fetch server inform
- Flexible support for static secrets or fetching all secrets in a vault.

## Requirements
- Ansible
- Ansible 2.18
- Python `requests` library
- A DVLS application identity (create at `{your-dvls-url}/administration/applications`).
- The application must have permissions to fetch the desired secrets.
Expand Down Expand Up @@ -107,7 +107,6 @@ Example response
"server": {
"accessURI": "https://example.dvls-server.com/",
"changed": false,
"expirationDate": "2030-12-31T23:59:59",
"failed": false,
"vaults": [
{
Expand All @@ -129,16 +128,16 @@ Example response

## Secrets definition

To access a particular field within a secret, you can use the format ```{{ secrets['name-or-id'].value }}```. Heres a breakdown of the available categories and their fields:
To access a particular field within a secret, you can use the format ```{{ secrets['name-or-id'].value }}```. Here's a breakdown of the available categories and their fields:

| **Category** | **Fields** |
|---------------------------|---------------------------------------------------------------------------|
| Username and password | `domain`, `password`, `username` |
| Connection string | `connectionString` |
| Secret | `password` |
| API key | `apiId`, `apiKey`, `tenantId` |
| **Category** | **Fields** |
|---------------------------|---------------------------------------------------------------------------------------------------------------------------|
| Username and password | `domain`, `password`, `username` |
| Connection string | `connectionString` |
| Secret | `password` |
| API key | `apiId`, `apiKey`, `tenantId` |
| SSH key | `domain`, `password`, `privateKeyData`, `privateKeyOverridePassword`, `privateKeyPassPhrase`, `publicKeyData`, `username` |
| Azure service principal | `clientId`, `clientSecret`, `tenantId` |
| Azure service principal | `clientId`, `clientSecret`, `tenantId` |


### Example using secret value
Expand Down
4 changes: 2 additions & 2 deletions galaxy.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace: devolutions
name: dvls
version: 1.2.1
version: 1.2.2
readme: README.md
authors:
- Danny Bédard <devops@devolutions.net>
Expand All @@ -16,4 +16,4 @@ repository: https://github.com/Devolutions/ansible-dvls
documentation: https://github.com/Devolutions/ansible-dvls
homepage: https://github.com/Devolutions/ansible-dvls
issues: https://github.com/Devolutions/ansible-dvls/issues
build_ignore: [.github, requirements.txt, tests]
build_ignore: [.github, .ruff.toml, requirements.txt, tests]
2 changes: 1 addition & 1 deletion meta/runtime.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
requires_ansible: '>=2.9.10'
requires_ansible: '~=2.18'
37 changes: 22 additions & 15 deletions plugins/module_utils/auth.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
import requests
import json
import traceback

try:
import requests
except ImportError:
HAS_REQUESTS_LIBRARY = False
REQUESTS_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
HAS_REQUESTS_LIBRARY_LIBRARY = True
REQUESTS_LIBRARY_IMPORT_ERROR = None


def login(server_base_url, app_key, app_secret):
login_url = f"{server_base_url}/api/v1/login"
login_data = {
"appKey": app_key,
"appSecret": app_secret
}
login_headers = {
"Content-Type": "application/json"
}
login_data = {"appKey": app_key, "appSecret": app_secret}
login_headers = {"Content-Type": "application/json"}

try:
response = requests.post(login_url, headers=login_headers, data=json.dumps(login_data))
response = requests.post(
login_url, headers=login_headers, data=json.dumps(login_data)
)
response.raise_for_status()
except Exception as e:
raise Exception(f"Failed to login: Unable to reach the server. Verify your network connection and server URL: {e}")
raise Exception(
f"Failed to login: Unable to reach the server. Verify your network connection and server URL: {e}"
)

auth_response = response.json()
token = auth_response.get('tokenId')
token = auth_response.get("tokenId")

if not token or token == "null":
raise Exception("Failed to login or obtain token.")

return token


def logout(server_base_url, token):
logout_url = f"{server_base_url}/api/v1/logout"
logout_headers = {
"Content-Type": "application/json",
"tokenId": token
}
logout_headers = {"Content-Type": "application/json", "tokenId": token}

requests.post(logout_url, headers=logout_headers)
return None
31 changes: 20 additions & 11 deletions plugins/module_utils/server.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import requests
import traceback

try:
import requests
except ImportError:
HAS_REQUESTS_LIBRARY = False
REQUESTS_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
HAS_REQUESTS_LIBRARY_LIBRARY = True
REQUESTS_LIBRARY_IMPORT_ERROR = None


def public_instance_information(server_base_url, token):
url = f"{server_base_url}/api/public-instance-information"
headers = {
"Content-Type": "application/json",
"tokenId": token
}
headers = {"Content-Type": "application/json", "tokenId": token}

try:
response = requests.get(url, headers=headers)
return response.json()
except Exception as e:
raise Exception(f"An error occurred while fetching public instance information: {e}")
raise Exception(
f"An error occurred while fetching public instance information: {e}"
)


def private_instance_information(server_base_url, token):
url = f"{server_base_url}/api/private-instance-information"
headers = {
"Content-Type": "application/json",
"tokenId": token
}
headers = {"Content-Type": "application/json", "tokenId": token}

try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
except Exception as e:
raise Exception(f"An error occurred while fetching private instance information: {e}")
raise Exception(
f"An error occurred while fetching private instance information: {e}"
)
19 changes: 11 additions & 8 deletions plugins/module_utils/utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from ansible_collections.devolutions.dvls.plugins.module_utils.vaults import get_vault_entry
from ansible_collections.devolutions.dvls.plugins.module_utils.vaults import (
get_vault_entry,
)


def get_sensible_value(server_base_url, token, vault_id, entries):
fetched_secrets = {}

if isinstance(entries, dict) and 'data' in entries:
entries = entries.get('data', [])
if isinstance(entries, dict) and "data" in entries:
entries = entries.get("data", [])

if not isinstance(entries, list):
return {"error": f"Expected list of entries, got {type(entries).__name__}"}
Expand All @@ -13,14 +16,14 @@ def get_sensible_value(server_base_url, token, vault_id, entries):
if not isinstance(secret, dict):
continue

entry_name = secret.get('name')
if not entry_name or 'id' not in secret:
entry_name = secret.get("name")
if not entry_name or "id" not in secret:
continue

try:
entry = get_vault_entry(server_base_url, token, vault_id, secret['id'])
if isinstance(entry, dict) and 'data' in entry:
fetched_secrets[entry_name] = entry['data']
entry = get_vault_entry(server_base_url, token, vault_id, secret["id"])
if isinstance(entry, dict) and "data" in entry:
fetched_secrets[entry_name] = entry["data"]
except Exception as e:
fetched_secrets[entry_name] = {"error": str(e)}

Expand Down
Loading