mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-09-08 19:27:18 +02:00
Compare commits
9 Commits
c6f49dc6e2
...
b6ee7b9f98
Author | SHA1 | Date | |
---|---|---|---|
b6ee7b9f98 | |||
67122800f3 | |||
bfd1a2ee70 | |||
076a2058cc | |||
9dc55c5893 | |||
81ef808191 | |||
8161dd1b6d | |||
ac72544b72 | |||
732607bbb6 |
@@ -6,7 +6,6 @@ import os
|
|||||||
import datetime
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def run_ansible_playbook(
|
def run_ansible_playbook(
|
||||||
inventory,
|
inventory,
|
||||||
modes,
|
modes,
|
||||||
@@ -81,6 +80,24 @@ def run_ansible_playbook(
|
|||||||
duration = end_time - start_time
|
duration = end_time - start_time
|
||||||
print(f"⏱️ Total execution time: {duration}\n")
|
print(f"⏱️ Total execution time: {duration}\n")
|
||||||
|
|
||||||
|
def validate_application_ids(inventory, app_ids):
|
||||||
|
"""
|
||||||
|
Abort the script if any application IDs are invalid, with detailed reasons.
|
||||||
|
"""
|
||||||
|
from utils.valid_deploy_id import ValidDeployId
|
||||||
|
validator = ValidDeployId()
|
||||||
|
invalid = validator.validate(inventory, app_ids)
|
||||||
|
if invalid:
|
||||||
|
print("\n❌ Detected invalid application_id(s):\n")
|
||||||
|
for app_id, status in invalid.items():
|
||||||
|
reasons = []
|
||||||
|
if not status['in_roles']:
|
||||||
|
reasons.append("not defined in roles (cymais)")
|
||||||
|
if not status['in_inventory']:
|
||||||
|
reasons.append("not found in inventory file")
|
||||||
|
print(f" - {app_id}: " + ", ".join(reasons))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
@@ -150,6 +167,7 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
validate_application_ids(args.inventory, args.id)
|
||||||
|
|
||||||
modes = {
|
modes = {
|
||||||
"mode_reset": args.reset,
|
"mode_reset": args.reset,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
database_id: "svc-db-{{ database_type }}"
|
database_id: "svc-db-{{ database_type }}"
|
||||||
database_instance: "{{ applications | get_app_conf(database_application_id, 'hostname', false) if applications | get_app_conf(database_application_id, 'features.central_database', False) else database_application_id }}"
|
database_instance: "{{ applications | get_app_conf(database_id, 'hostname', True) if applications | get_app_conf(database_application_id, 'features.central_database', False) else database_application_id }}"
|
||||||
database_host: "{{ applications | get_app_conf(database_application_id, 'hostname', false) if applications | get_app_conf(database_application_id, 'features.central_database', False) else 'database' }}"
|
database_host: "{{ applications | get_app_conf(database_id, 'hostname', True) if applications | get_app_conf(database_application_id, 'features.central_database', False) else 'database' }}"
|
||||||
database_name: "{{ applications | get_app_conf(database_application_id, 'database.name', false, database_application_id ) }}" # The overwritte configuration is needed by bigbluebutton
|
database_name: "{{ applications | get_app_conf(database_application_id, 'database.name', false, database_application_id ) }}" # The overwritte configuration is needed by bigbluebutton
|
||||||
database_username: "{{ applications | get_app_conf(database_application_id, 'database.username', false, database_application_id )}}" # The overwritte configuration is needed by bigbluebutton
|
database_username: "{{ applications | get_app_conf(database_application_id, 'database.username', false, database_application_id )}}" # The overwritte configuration is needed by bigbluebutton
|
||||||
database_password: "{{ applications | get_app_conf(database_application_id, 'credentials.database_password', true) }}"
|
database_password: "{{ applications | get_app_conf(database_application_id, 'credentials.database_password', true) }}"
|
||||||
|
@@ -11,12 +11,11 @@ networks:
|
|||||||
%}
|
%}
|
||||||
svc-db-openldap:
|
svc-db-openldap:
|
||||||
external: true
|
external: true
|
||||||
{% endif %}
|
{% else %}
|
||||||
default:
|
default:
|
||||||
{% if
|
{% if
|
||||||
application_id in networks.local and
|
application_id in networks.local and
|
||||||
networks.local[application_id].subnet is defined and
|
networks.local[application_id].subnet is defined
|
||||||
application_id != 'svc-db-openldap'
|
|
||||||
%}
|
%}
|
||||||
name: {{ application_id }}
|
name: {{ application_id }}
|
||||||
driver: bridge
|
driver: bridge
|
||||||
@@ -25,4 +24,5 @@ networks:
|
|||||||
config:
|
config:
|
||||||
- subnet: {{networks.local[application_id].subnet}}
|
- subnet: {{networks.local[application_id].subnet}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{{ "\n" }}
|
{{ "\n" }}
|
@@ -1,4 +1,8 @@
|
|||||||
version: "latest"
|
|
||||||
hostname: "svc-db-mariadb"
|
hostname: "svc-db-mariadb"
|
||||||
network: "<< defaults_applications[svc-db-mariadb].hostname >>"
|
network: "<< defaults_applications[svc-db-mariadb].hostname >>"
|
||||||
volume: "<< defaults_applications[svc-db-mariadb].hostname >>_data"
|
docker:
|
||||||
|
services:
|
||||||
|
mariadb:
|
||||||
|
version: "latest"
|
||||||
|
image: "mariadb"
|
||||||
|
volume: "<< defaults_applications[svc-db-mariadb].hostname >>_data"
|
||||||
|
30
roles/svc-db-mariadb/tasks/init.yml
Normal file
30
roles/svc-db-mariadb/tasks/init.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
- name: "Create database: {{ database_name }}"
|
||||||
|
mysql_db:
|
||||||
|
name: "{{ database_name }}"
|
||||||
|
state: present
|
||||||
|
login_user: root
|
||||||
|
login_password: "{{ mariadb_root_pwd }}"
|
||||||
|
login_host: 127.0.0.1
|
||||||
|
login_port: "{{ database_port }}"
|
||||||
|
encoding: "{{ database_encoding }}"
|
||||||
|
collation: "{{ database_collation }}"
|
||||||
|
|
||||||
|
- name: "Create database user: {{ database_username }}"
|
||||||
|
mysql_user:
|
||||||
|
name: "{{database_username}}"
|
||||||
|
password: "{{database_password}}"
|
||||||
|
host: "%"
|
||||||
|
priv: '{{database_name}}.*:ALL'
|
||||||
|
state: present
|
||||||
|
login_user: root
|
||||||
|
login_password: "{{mariadb_root_pwd}}"
|
||||||
|
login_host: 127.0.0.1
|
||||||
|
login_port: "{{database_port}}"
|
||||||
|
|
||||||
|
# Deactivated due to https://chatgpt.com/share/683ba14b-0e74-800f-9ad1-a8979bc77093
|
||||||
|
# @todo Remove if this works fine in the future.
|
||||||
|
#- name: Grant database privileges
|
||||||
|
# ansible.builtin.shell:
|
||||||
|
# cmd: "docker exec {{mariadb_hostname }} mariadb -u root -p{{ mariadb_root_pwd }} -e \"GRANT ALL PRIVILEGES ON `{{database_name}}`.* TO '{{database_username}}'@'%';\""
|
||||||
|
# args:
|
||||||
|
# executable: /bin/bash
|
@@ -1,29 +1,29 @@
|
|||||||
- name: Create Docker network for MariaDB
|
- name: Create Docker network for MariaDB
|
||||||
docker_network:
|
docker_network:
|
||||||
name: "{{ applications['svc-db-mariadb'].network }}"
|
name: "{{ mariadb_network_name }}"
|
||||||
state: present
|
state: present
|
||||||
ipam_config:
|
ipam_config:
|
||||||
- subnet: "{{ networks.local['svc-db-mariadb'].subnet }}"
|
- subnet: "{{ mariadb_subnet }}"
|
||||||
when: run_once_docker_mariadb is not defined
|
when: run_once_docker_mariadb is not defined
|
||||||
|
|
||||||
- name: install MariaDB
|
- name: install MariaDB
|
||||||
docker_container:
|
docker_container:
|
||||||
name: "{{ mariadb_hostname }}"
|
name: "{{ mariadb_hostname }}"
|
||||||
image: "mariadb:{{applications['svc-db-mariadb'].version}}"
|
image: "{{ mariadb_image }}:{{ mariadb_version}}"
|
||||||
detach: yes
|
detach: yes
|
||||||
env:
|
env:
|
||||||
MARIADB_ROOT_PASSWORD: "{{mariadb_root_pwd}}"
|
MARIADB_ROOT_PASSWORD: "{{ mariadb_root_pwd }}"
|
||||||
MARIADB_AUTO_UPGRADE: "1"
|
MARIADB_AUTO_UPGRADE: "1"
|
||||||
networks:
|
networks:
|
||||||
- name: "{{ applications['svc-db-mariadb'].network }}"
|
- name: "{{ mariadb_network_name }}"
|
||||||
volumes:
|
volumes:
|
||||||
- "{{ applications['svc-db-mariadb'].volume }}:/var/lib/mysql"
|
- "{{ mariadb_volume }}:/var/lib/mysql"
|
||||||
published_ports:
|
published_ports:
|
||||||
- "127.0.0.1:{{database_port}}:3306" # can be that this will be removed if all applications use sockets
|
- "127.0.0.1:{{ mariadb_port }}:3306" # can be that this will be removed if all applications use sockets
|
||||||
command: "--transaction-isolation=READ-COMMITTED --binlog-format=ROW" #for nextcloud
|
command: "--transaction-isolation=READ-COMMITTED --binlog-format=ROW" #for nextcloud
|
||||||
restart_policy: "{{docker_restart_policy}}"
|
restart_policy: "{{ docker_restart_policy }}"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: "/usr/bin/mariadb --user=root --password={{mariadb_root_pwd}} --execute \"SHOW DATABASES;\""
|
test: "/usr/bin/mariadb --user=root --password={{ mariadb_root_pwd }} --execute \"SHOW DATABASES;\""
|
||||||
interval: 3s
|
interval: 3s
|
||||||
timeout: 1s
|
timeout: 1s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -51,36 +51,9 @@
|
|||||||
- setup_mariadb_container_result.changed
|
- setup_mariadb_container_result.changed
|
||||||
- run_once_docker_mariadb is not defined
|
- run_once_docker_mariadb is not defined
|
||||||
|
|
||||||
- name: "Create database: {{ database_name }}"
|
- name: "Initialize database for '{{ database_name }}'"
|
||||||
mysql_db:
|
include_tasks: init.yml
|
||||||
name: "{{ database_name }}"
|
when: "{{ mariadb_init }}"
|
||||||
state: present
|
|
||||||
login_user: root
|
|
||||||
login_password: "{{ mariadb_root_pwd }}"
|
|
||||||
login_host: 127.0.0.1
|
|
||||||
login_port: "{{ database_port }}"
|
|
||||||
encoding: "{{ database_encoding }}"
|
|
||||||
collation: "{{ database_collation }}"
|
|
||||||
|
|
||||||
- name: "Create database user: {{ database_username }}"
|
|
||||||
mysql_user:
|
|
||||||
name: "{{database_username}}"
|
|
||||||
password: "{{database_password}}"
|
|
||||||
host: "%"
|
|
||||||
priv: '{{database_name}}.*:ALL'
|
|
||||||
state: present
|
|
||||||
login_user: root
|
|
||||||
login_password: "{{mariadb_root_pwd}}"
|
|
||||||
login_host: 127.0.0.1
|
|
||||||
login_port: "{{database_port}}"
|
|
||||||
|
|
||||||
# Deactivated due to https://chatgpt.com/share/683ba14b-0e74-800f-9ad1-a8979bc77093
|
|
||||||
# @todo Remove if this works fine in the future.
|
|
||||||
#- name: Grant database privileges
|
|
||||||
# ansible.builtin.shell:
|
|
||||||
# cmd: "docker exec {{mariadb_hostname }} mariadb -u root -p{{ mariadb_root_pwd }} -e \"GRANT ALL PRIVILEGES ON `{{database_name}}`.* TO '{{database_username}}'@'%';\""
|
|
||||||
# args:
|
|
||||||
# executable: /bin/bash
|
|
||||||
|
|
||||||
- name: run the docker_mariadb tasks once
|
- name: run the docker_mariadb tasks once
|
||||||
set_fact:
|
set_fact:
|
||||||
|
@@ -1,3 +1,11 @@
|
|||||||
application_id: svc-db-mariadb
|
application_id: svc-db-mariadb
|
||||||
mariadb_hostname: "{{ applications | get_app_conf(application_id, 'hostname', True) }}"
|
mariadb_hostname: "{{ applications | get_app_conf(application_id,'hostname', True) }}"
|
||||||
mariadb_root_pwd: "{{ applications['svc-db-mariadb'].credentials.root_password }}"
|
mariadb_root_pwd: "{{ applications | get_app_conf(application_id,'credentials.root_password', True) }}"
|
||||||
|
mariadb_init: "{{ database_username is defined and database_password is defined and database_name is defined }}"
|
||||||
|
mariadb_subnet: "{{ networks.local['svc-db-mariadb'].subnet }}"
|
||||||
|
mariadb_network_name: "{{ applications | get_app_conf(application_id,'network', True) }}"
|
||||||
|
mariadb_volume: "{{ applications | get_app_conf(application_id,'docker.services.mariadb.volume', True) }}"
|
||||||
|
mariadb_image: "{{ applications | get_app_conf(application_id,'docker.services.mariadb.image','mariadb', True) }}"
|
||||||
|
mariadb_version: "{{ applications | get_app_conf(application_id,'docker.services.mariadb.version', True) }}"
|
||||||
|
mariadb_port: "{{ database_port | default(ports.localhost.database[ application_id ]) }}"
|
||||||
|
|
||||||
|
@@ -4,15 +4,21 @@ network:
|
|||||||
local: True # Activates local network. Necessary for LDIF import routines
|
local: True # Activates local network. Necessary for LDIF import routines
|
||||||
docker: True # Activates docker network to allow other docker containers to connect
|
docker: True # Activates docker network to allow other docker containers to connect
|
||||||
public: False # Set to true in inventory file if you want to expose the LDAP port to the internet
|
public: False # Set to true in inventory file if you want to expose the LDAP port to the internet
|
||||||
images:
|
docker:
|
||||||
openldap: "bitnami/openldap:latest"
|
services:
|
||||||
|
openldap:
|
||||||
|
image: "bitnami/openldap"
|
||||||
|
version: "latest"
|
||||||
|
container: "<< defaults_applications[svc-db-openldap].hostname >>"
|
||||||
webinterface: "lam" # The webinterface which should be used. Possible: lam and phpldapadmin
|
webinterface: "lam" # The webinterface which should be used. Possible: lam and phpldapadmin
|
||||||
features:
|
features:
|
||||||
ldap: true
|
ldap: true
|
||||||
import:
|
provisioning:
|
||||||
# Here it's possible to define what can be imported.
|
# Here it's possible to define what should be imported and updated.
|
||||||
# It doesn't make sense to let the import run everytime because its very time consuming
|
# It doesn't make sense to let the import run everytime because its very time consuming
|
||||||
credentials: true
|
configuration: true # E.g. MemberOf and Hashed Password Configuration
|
||||||
schemas: true
|
credentials: true # Administrator Password
|
||||||
entries: true
|
schemas: true # E.g. Nextcloud, Openssl
|
||||||
users: true
|
users: true # E.g. User, group and role entries
|
||||||
|
groups: true # Roles and Groups import
|
||||||
|
update: true # User Class updates
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
- name: Load memberof module from file in OpenLDAP container
|
- name: Load memberof module from file in OpenLDAP container
|
||||||
shell: >
|
shell: >
|
||||||
docker exec -i {{ applications | get_app_conf(application_id, 'hostname', True) }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}configuration/01_member_of_configuration.ldif
|
docker exec -i {{ applications | get_app_conf(application_id, 'hostname', True) }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{openldap_ldif_docker_path}}configuration/01_member_of_configuration.ldif
|
||||||
listen:
|
listen:
|
||||||
- "Import configuration LDIF files"
|
- "Import configuration LDIF files"
|
||||||
- "Import all LDIF files"
|
- "Import all LDIF files"
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
- name: Refint Module Activation for OpenLDAP
|
- name: Refint Module Activation for OpenLDAP
|
||||||
shell: >
|
shell: >
|
||||||
docker exec -i {{ applications | get_app_conf(application_id, 'hostname', True) }} ldapadd -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}configuration/02_member_of_configuration.ldif
|
docker exec -i {{ applications | get_app_conf(application_id, 'hostname', True) }} ldapadd -Y EXTERNAL -H ldapi:/// -f {{openldap_ldif_docker_path}}configuration/02_member_of_configuration.ldif
|
||||||
listen:
|
listen:
|
||||||
- "Import configuration LDIF files"
|
- "Import configuration LDIF files"
|
||||||
- "Import all LDIF files"
|
- "Import all LDIF files"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
- name: "Import schemas"
|
- name: "Import schemas"
|
||||||
shell: >
|
shell: >
|
||||||
docker exec -i {{ applications | get_app_conf(application_id, 'hostname', True) }} ldapadd -Y EXTERNAL -H ldapi:/// -f "{{ldif_docker_path}}schema/{{ item | basename | regex_replace('\.j2$', '') }}"
|
docker exec -i {{ applications | get_app_conf(application_id, 'hostname', True) }} ldapadd -Y EXTERNAL -H ldapi:/// -f "{{openldap_ldif_docker_path}}schema/{{ item | basename | regex_replace('\.j2$', '') }}"
|
||||||
register: ldapadd_result
|
register: ldapadd_result
|
||||||
changed_when: "'adding new entry' in ldapadd_result.stdout"
|
changed_when: "'adding new entry' in ldapadd_result.stdout"
|
||||||
failed_when: ldapadd_result.rc not in [0, 80]
|
failed_when: ldapadd_result.rc not in [0, 80]
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
- name: Refint Overlay Configuration for OpenLDAP
|
- name: Refint Overlay Configuration for OpenLDAP
|
||||||
shell: >
|
shell: >
|
||||||
docker exec -i {{ applications | get_app_conf(application_id, 'hostname', True) }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}configuration/03_member_of_configuration.ldif
|
docker exec -i {{ applications | get_app_conf(application_id, 'hostname', True) }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{openldap_ldif_docker_path}}configuration/03_member_of_configuration.ldif
|
||||||
listen:
|
listen:
|
||||||
- "Import configuration LDIF files"
|
- "Import configuration LDIF files"
|
||||||
- "Import all LDIF files"
|
- "Import all LDIF files"
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
- name: "Import users, groups, etc. to LDAP"
|
- name: "Import users, groups, etc. to LDAP"
|
||||||
shell: >
|
shell: >
|
||||||
docker exec -i {{ applications | get_app_conf(application_id, 'hostname', True) }} ldapadd -x -D "{{ldap.dn.administrator.data}}" -w "{{ldap.bind_credential}}" -c -f "{{ldif_docker_path}}data/{{ item | basename | regex_replace('\.j2$', '') }}"
|
docker exec -i {{ applications | get_app_conf(application_id, 'hostname', True) }} ldapadd -x -D "{{ldap.dn.administrator.data}}" -w "{{ldap.bind_credential}}" -c -f "{{openldap_ldif_docker_path}}data/{{ item | basename | regex_replace('\.j2$', '') }}"
|
||||||
register: ldapadd_result
|
register: ldapadd_result
|
||||||
changed_when: "'adding new entry' in ldapadd_result.stdout"
|
changed_when: "'adding new entry' in ldapadd_result.stdout"
|
||||||
failed_when: ldapadd_result.rc not in [0, 20, 68, 65]
|
failed_when: ldapadd_result.rc not in [0, 20, 68, 65]
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
- name: Ensure LDAP users exist
|
- name: Ensure LDAP users exist
|
||||||
community.general.ldap_entry:
|
community.general.ldap_entry:
|
||||||
dn: "{{ ldap.user.attributes.id }}={{ item.key }},{{ ldap.dn.ou.users }}"
|
dn: "{{ ldap.user.attributes.id }}={{ item.key }},{{ ldap.dn.ou.users }}"
|
||||||
server_uri: "{{ ldap_server_uri }}"
|
server_uri: "{{ openldap_server_uri }}"
|
||||||
bind_dn: "{{ ldap.dn.administrator.data }}"
|
bind_dn: "{{ ldap.dn.administrator.data }}"
|
||||||
bind_pw: "{{ ldap.bind_credential }}"
|
bind_pw: "{{ ldap.bind_credential }}"
|
||||||
objectClass: "{{ ldap.user.objects.structural }}"
|
objectClass: "{{ ldap.user.objects.structural }}"
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
- name: Ensure required objectClass values and mail address are present
|
- name: Ensure required objectClass values and mail address are present
|
||||||
community.general.ldap_attrs:
|
community.general.ldap_attrs:
|
||||||
dn: "{{ ldap.user.attributes.id }}={{ item.key }},{{ ldap.dn.ou.users }}"
|
dn: "{{ ldap.user.attributes.id }}={{ item.key }},{{ ldap.dn.ou.users }}"
|
||||||
server_uri: "{{ ldap_server_uri }}"
|
server_uri: "{{ openldap_server_uri }}"
|
||||||
bind_dn: "{{ ldap.dn.administrator.data }}"
|
bind_dn: "{{ ldap.dn.administrator.data }}"
|
||||||
bind_pw: "{{ ldap.bind_credential }}"
|
bind_pw: "{{ ldap.bind_credential }}"
|
||||||
attributes:
|
attributes:
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
- name: "Ensure container for application roles exists"
|
- name: "Ensure container for application roles exists"
|
||||||
community.general.ldap_entry:
|
community.general.ldap_entry:
|
||||||
dn: "{{ ldap.dn.ou.roles }}"
|
dn: "{{ ldap.dn.ou.roles }}"
|
||||||
server_uri: "{{ ldap_server_uri }}"
|
server_uri: "{{ openldap_server_uri }}"
|
||||||
bind_dn: "{{ ldap.dn.administrator.data }}"
|
bind_dn: "{{ ldap.dn.administrator.data }}"
|
||||||
bind_pw: "{{ ldap.bind_credential }}"
|
bind_pw: "{{ ldap.bind_credential }}"
|
||||||
objectClass: organizationalUnit
|
objectClass: organizationalUnit
|
@@ -1,6 +1,6 @@
|
|||||||
- name: Gather all users with their current objectClass list
|
- name: Gather all users with their current objectClass list
|
||||||
community.general.ldap_search:
|
community.general.ldap_search:
|
||||||
server_uri: "{{ ldap_server_uri }}"
|
server_uri: "{{ openldap_server_uri }}"
|
||||||
bind_dn: "{{ ldap.dn.administrator.data }}"
|
bind_dn: "{{ ldap.dn.administrator.data }}"
|
||||||
bind_pw: "{{ ldap.bind_credential }}"
|
bind_pw: "{{ ldap.bind_credential }}"
|
||||||
dn: "{{ ldap.dn.ou.users }}"
|
dn: "{{ ldap.dn.ou.users }}"
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
- name: Add only missing auxiliary classes
|
- name: Add only missing auxiliary classes
|
||||||
community.general.ldap_attrs:
|
community.general.ldap_attrs:
|
||||||
server_uri: "{{ ldap_server_uri }}"
|
server_uri: "{{ openldap_server_uri }}"
|
||||||
bind_dn: "{{ ldap.dn.administrator.data }}"
|
bind_dn: "{{ ldap.dn.administrator.data }}"
|
||||||
bind_pw: "{{ ldap.bind_credential }}"
|
bind_pw: "{{ ldap.bind_credential }}"
|
||||||
dn: "{{ item.dn }}"
|
dn: "{{ item.dn }}"
|
@@ -1,7 +1,7 @@
|
|||||||
- name: "Create LDIF files at {{ ldif_host_path }}{{ folder }}"
|
- name: "Create LDIF files at {{ openldap_ldif_host_path }}{{ folder }}"
|
||||||
template:
|
template:
|
||||||
src: "{{ item }}"
|
src: "{{ item }}"
|
||||||
dest: "{{ ldif_host_path }}{{ folder }}/{{ item | basename | regex_replace('\\.j2$', '') }}"
|
dest: "{{ openldap_ldif_host_path }}{{ folder }}/{{ item | basename | regex_replace('\\.j2$', '') }}"
|
||||||
mode: '770'
|
mode: '770'
|
||||||
loop: >-
|
loop: >-
|
||||||
{{
|
{{
|
||||||
|
@@ -22,28 +22,30 @@
|
|||||||
name: "{{ applications | get_app_conf(application_id, 'network.name', True) }}"
|
name: "{{ applications | get_app_conf(application_id, 'network.name', True) }}"
|
||||||
state: present
|
state: present
|
||||||
ipam_config:
|
ipam_config:
|
||||||
- subnet: "{{ networks.local['svc-db-openldap'].subnet }}"
|
- subnet: "{{ networks.local[application_id].subnet }}"
|
||||||
|
|
||||||
- meta: flush_handlers
|
- meta: flush_handlers
|
||||||
|
|
||||||
- name: "Wait for LDAP to be available"
|
- name: "Wait for LDAP to be available"
|
||||||
wait_for:
|
wait_for:
|
||||||
host: "127.0.0.1"
|
host: "127.0.0.1"
|
||||||
port: "{{ ports.localhost.ldap['svc-db-openldap'] }}"
|
port: "{{ ports.localhost.ldap[application_id] }}"
|
||||||
delay: 5
|
delay: 5
|
||||||
timeout: 120
|
timeout: 120
|
||||||
state: started
|
state: started
|
||||||
|
|
||||||
- name: "Reset LDAP Credentials"
|
- name: "Reset LDAP Credentials"
|
||||||
include_tasks: 01_credentials.yml
|
include_tasks: 01_credentials.yml
|
||||||
when: applications | get_app_conf(application_id, 'network.local', True)
|
when:
|
||||||
|
- applications | get_app_conf(application_id, 'network.local', True)
|
||||||
|
- applications | get_app_conf(application_id, 'provisioning.credentials', True)
|
||||||
|
|
||||||
- name: "create directory {{ldif_host_path}}{{item}}"
|
- name: "create directory {{openldap_ldif_host_path}}{{item}}"
|
||||||
file:
|
file:
|
||||||
path: "{{ldif_host_path}}{{item}}"
|
path: "{{openldap_ldif_host_path}}{{item}}"
|
||||||
state: directory
|
state: directory
|
||||||
mode: 0755
|
mode: 0755
|
||||||
loop: "{{ldif_types}}"
|
loop: "{{openldap_ldif_types}}"
|
||||||
|
|
||||||
- name: "Import LDIF Configuration"
|
- name: "Import LDIF Configuration"
|
||||||
include_tasks: ldifs_creation.yml
|
include_tasks: ldifs_creation.yml
|
||||||
@@ -51,6 +53,7 @@
|
|||||||
- configuration
|
- configuration
|
||||||
loop_control:
|
loop_control:
|
||||||
loop_var: folder
|
loop_var: folder
|
||||||
|
when: applications | get_app_conf(application_id, 'provisioning.configuration', True)
|
||||||
|
|
||||||
- name: flush LDIF handlers
|
- name: flush LDIF handlers
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
@@ -63,16 +66,20 @@
|
|||||||
|
|
||||||
- name: "Include Schemas (if enabled)"
|
- name: "Include Schemas (if enabled)"
|
||||||
include_tasks: 02_schemas.yml
|
include_tasks: 02_schemas.yml
|
||||||
|
when: applications | get_app_conf(application_id, 'provisioning.schemas', True)
|
||||||
|
|
||||||
- name: "Import LDAP Entries (if enabled)"
|
- name: "Import LDAP Entries (if enabled)"
|
||||||
include_tasks: 03_entries.yml
|
include_tasks: 03_users.yml
|
||||||
|
when: applications | get_app_conf(application_id, 'provisioning.users', True)
|
||||||
|
|
||||||
- name: "Import LDIF Data (if enabled)"
|
- name: "Import LDIF Data (if enabled)"
|
||||||
include_tasks: ldifs_creation.yml
|
include_tasks: ldifs_creation.yml
|
||||||
loop:
|
loop:
|
||||||
- data
|
- groups
|
||||||
loop_control:
|
loop_control:
|
||||||
loop_var: folder
|
loop_var: folder
|
||||||
|
when: applications | get_app_conf(application_id, 'provisioning.groups', True)
|
||||||
|
|
||||||
- name: "Add Objects to all users"
|
- name: "Add Objects to all users"
|
||||||
include_tasks: 04_user_updates.yml
|
include_tasks: 04_update.yml
|
||||||
|
when: applications | get_app_conf(application_id, 'provisioning.update', True)
|
@@ -13,9 +13,9 @@
|
|||||||
- "( 1.3.6.1.4.1.99999.2 NAME 'nextcloudUser' DESC 'Auxiliary class for Nextcloud attributes' AUXILIARY MAY ( {{ ldap.user.attributes.nextcloud_quota }} ) )"
|
- "( 1.3.6.1.4.1.99999.2 NAME 'nextcloudUser' DESC 'Auxiliary class for Nextcloud attributes' AUXILIARY MAY ( {{ ldap.user.attributes.nextcloud_quota }} ) )"
|
||||||
command: >
|
command: >
|
||||||
ldapsm
|
ldapsm
|
||||||
-s {{ ldap_server_uri }}
|
-s {{ openldap_server_uri }}
|
||||||
-D '{{ ldap_bind_dn }}'
|
-D '{{ openldap_bind_dn }}'
|
||||||
-W '{{ ldap_bind_pw }}'
|
-W '{{ openldap_bind_pw }}'
|
||||||
-n {{ schema_name }}
|
-n {{ schema_name }}
|
||||||
{% for at in attribute_defs %}
|
{% for at in attribute_defs %}
|
||||||
-a "{{ at }}"
|
-a "{{ at }}"
|
||||||
|
@@ -21,9 +21,9 @@
|
|||||||
|
|
||||||
command: >
|
command: >
|
||||||
ldapsm
|
ldapsm
|
||||||
-s {{ ldap_server_uri }}
|
-s {{ openldap_server_uri }}
|
||||||
-D '{{ ldap_bind_dn }}'
|
-D '{{ openldap_bind_dn }}'
|
||||||
-W '{{ ldap_bind_pw }}'
|
-W '{{ openldap_bind_pw }}'
|
||||||
-n {{ schema_name }}
|
-n {{ schema_name }}
|
||||||
{% for at in attribute_defs %}
|
{% for at in attribute_defs %}
|
||||||
-a "{{ at }}"
|
-a "{{ at }}"
|
||||||
|
@@ -1,20 +1,20 @@
|
|||||||
{% include 'roles/docker-compose/templates/base.yml.j2' %}
|
{% include 'roles/docker-compose/templates/base.yml.j2' %}
|
||||||
|
|
||||||
application:
|
application:
|
||||||
image: "{{ applications | get_app_conf(application_id, 'images.openldap', True) }}"
|
image: "{{ openldap_image }}:{{ openldap_version }}"
|
||||||
container_name: {{ applications | get_app_conf(application_id, 'hostname', True) }}
|
container_name: "{{ openldap_container }}"
|
||||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||||
{% if applications | get_app_conf(application_id, 'network.public', True) | bool or applications | get_app_conf(application_id, 'network.local', True) | bool %}
|
{% if openldap_network_expose_local %}
|
||||||
ports:
|
ports:
|
||||||
- 127.0.0.1:{{ports.localhost.ldap['svc-db-openldap']}}:{{ldap_docker_port}}
|
- 127.0.0.1:{{ports.localhost.ldap['svc-db-openldap']}}:{{openldap_docker_port_open}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
volumes:
|
volumes:
|
||||||
- 'data:/bitnami/openldap'
|
- 'data:/bitnami/openldap'
|
||||||
- '{{ldif_host_path}}:{{ldif_docker_path}}:ro'
|
- '{{openldap_ldif_host_path}}:{{openldap_ldif_docker_path}}:ro'
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: >
|
test: >
|
||||||
bash -c '
|
bash -c '
|
||||||
ldapsearch -x -H ldap://localhost:{{ ldap_docker_port }} \
|
ldapsearch -x -H ldap://localhost:{{ openldap_docker_port_open }} \
|
||||||
-D "{{ ldap.dn.administrator.data }}" -w "{{ ldap.bind_credential }}" -b "{{ ldap.dn.root }}" > /dev/null \
|
-D "{{ ldap.dn.administrator.data }}" -w "{{ ldap.bind_credential }}" -b "{{ ldap.dn.root }}" > /dev/null \
|
||||||
&& ldapsearch -Y EXTERNAL -H ldapi:/// \
|
&& ldapsearch -Y EXTERNAL -H ldapi:/// \
|
||||||
-b cn=config "(&(objectClass=olcOverlayConfig)(olcOverlay=memberof))" \
|
-b cn=config "(&(objectClass=olcOverlayConfig)(olcOverlay=memberof))" \
|
||||||
@@ -24,5 +24,6 @@
|
|||||||
|
|
||||||
{% include 'roles/docker-compose/templates/volumes.yml.j2' %}
|
{% include 'roles/docker-compose/templates/volumes.yml.j2' %}
|
||||||
data:
|
data:
|
||||||
|
name: "{{ openldap_volume }}"
|
||||||
|
|
||||||
{% include 'roles/docker-compose/templates/networks.yml.j2' %}
|
{% include 'roles/docker-compose/templates/networks.yml.j2' %}
|
@@ -18,9 +18,9 @@ LDAP_CONFIG_ADMIN_USERNAME= {{applications | get_app_conf(application_id, 'users
|
|||||||
LDAP_CONFIG_ADMIN_PASSWORD= {{applications | get_app_conf(application_id, 'credentials.administrator_password', True)}}
|
LDAP_CONFIG_ADMIN_PASSWORD= {{applications | get_app_conf(application_id, 'credentials.administrator_password', True)}}
|
||||||
|
|
||||||
# Network
|
# Network
|
||||||
LDAP_PORT_NUMBER= {{ldap_docker_port}} # Route to default port
|
LDAP_PORT_NUMBER= {{openldap_docker_port_open}} # Route to default port
|
||||||
LDAP_ENABLE_TLS= no # Using nginx proxy for tls
|
LDAP_ENABLE_TLS= no # Using nginx proxy for tls
|
||||||
LDAP_LDAPS_PORT_NUMBER= {{ldaps_docker_port}} # Port used for TLS secure traffic. Priviledged port is supported (e.g. 636). Default: 1636 (non privileged port).
|
LDAP_LDAPS_PORT_NUMBER= {{openldap_docker_port_secure}} # Port used for TLS secure traffic. Priviledged port is supported (e.g. 636). Default: 1636 (non privileged port).
|
||||||
|
|
||||||
# Security
|
# Security
|
||||||
LDAP_ALLOW_ANON_BINDING= no # Allow anonymous bindings to the LDAP server. Default: yes.
|
LDAP_ALLOW_ANON_BINDING= no # Allow anonymous bindings to the LDAP server. Default: yes.
|
@@ -1,17 +1,24 @@
|
|||||||
application_id: "svc-db-openldap"
|
application_id: "svc-db-openldap"
|
||||||
|
|
||||||
# LDAP Variables
|
# LDAP Variables
|
||||||
ldaps_docker_port: 636
|
openldap_docker_port_secure: 636
|
||||||
ldap_docker_port: 389
|
openldap_docker_port_open: 389
|
||||||
ldap_server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap['svc-db-openldap'] }}"
|
openldap_server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap[application_id] }}"
|
||||||
ldap_hostname: "{{ applications | get_app_conf(application_id, 'hostname', True) }}"
|
openldap_hostname: "{{ applications | get_app_conf(application_id, 'hostname', True) }}"
|
||||||
ldap_bind_dn: "{{ ldap.dn.administrator.configuration }}"
|
openldap_bind_dn: "{{ ldap.dn.administrator.configuration }}"
|
||||||
ldap_bind_pw: "{{ applications | get_app_conf(application_id, 'credentials.administrator_password', True) }}"
|
openldap_bind_pw: "{{ applications | get_app_conf(application_id, 'credentials.administrator_password', True) }}"
|
||||||
|
|
||||||
# LDIF Variables
|
# LDIF Variables
|
||||||
ldif_host_path: "{{docker_compose.directories.volumes}}ldif/"
|
openldap_ldif_host_path: "{{docker_compose.directories.volumes}}ldif/"
|
||||||
ldif_docker_path: "/tmp/ldif/"
|
openldap_ldif_docker_path: "/tmp/ldif/"
|
||||||
ldif_types:
|
openldap_ldif_types:
|
||||||
- configuration
|
- configuration
|
||||||
- data
|
- data
|
||||||
- schema # Don't know if this is still needed, it's now setup via tasks
|
- schema # Don't know if this is still needed, it's now setup via tasks
|
||||||
|
|
||||||
|
openldap_container: "{{ applications | get_app_conf(application_id, 'docker.services.openldap.container', True) }}"
|
||||||
|
openldap_image: "{{ applications | get_app_conf(application_id, 'docker.services.openldap.image', True) }}"
|
||||||
|
openldap_version: "{{ applications | get_app_conf(application_id, 'docker.services.openldap.version', True) }}"
|
||||||
|
openldap_volume: "{{ application_id }}_data"
|
||||||
|
|
||||||
|
openldap_network_expose_local: "{{ applications | get_app_conf(application_id, 'network.public', True) | bool or applications | get_app_conf(application_id, 'network.local', True) | bool }}"
|
@@ -1,6 +1,5 @@
|
|||||||
hostname: "svc-db-postgres"
|
hostname: "svc-db-postgres"
|
||||||
network: "<< defaults_applications[svc-db-postgres].hostname >>"
|
network: "<< defaults_applications[svc-db-postgres].hostname >>"
|
||||||
volume: "<< defaults_applications[svc-db-postgres].hostname >>"
|
|
||||||
docker:
|
docker:
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
@@ -8,4 +7,5 @@ docker:
|
|||||||
image: postgis/postgis
|
image: postgis/postgis
|
||||||
# Please set an version in your inventory file!
|
# Please set an version in your inventory file!
|
||||||
# Rolling release isn't recommended
|
# Rolling release isn't recommended
|
||||||
version: "latest"
|
version: "latest"
|
||||||
|
volume: "<< defaults_applications[svc-db-postgres].hostname >>_data"
|
@@ -1,25 +1,25 @@
|
|||||||
- name: Create Docker network for PostgreSQL
|
- name: Create Docker network for PostgreSQL
|
||||||
docker_network:
|
docker_network:
|
||||||
name: "{{ applications | get_app_conf(application_id, 'network', True) }}"
|
name: "{{ postgres_network_name }}"
|
||||||
state: present
|
state: present
|
||||||
ipam_config:
|
ipam_config:
|
||||||
- subnet: "{{ networks.local['svc-db-postgres'].subnet }}"
|
- subnet: "{{ postgres_subnet }}"
|
||||||
when: run_once_docker_postgres is not defined
|
when: run_once_docker_postgres is not defined
|
||||||
|
|
||||||
- name: Install PostgreSQL
|
- name: Install PostgreSQL
|
||||||
docker_container:
|
docker_container:
|
||||||
name: "{{ applications | get_app_conf(application_id, 'hostname', True) }}"
|
name: "{{ postgres_hostname }}"
|
||||||
image: "{{ applications | get_app_conf(application_id, 'docker.services.postgres.image', True) }}:{{ applications | get_app_conf(application_id, 'docker.services.postgres.version', True) }}"
|
image: "{{ postgres_image }}:{{ postgres_version }}"
|
||||||
detach: yes
|
detach: yes
|
||||||
env:
|
env:
|
||||||
POSTGRES_PASSWORD: "{{ applications | get_app_conf(application_id, 'credentials.postgres_password', True) }}"
|
POSTGRES_PASSWORD: "{{ postgres_password }}"
|
||||||
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C" # Necessary for web-app-matrix
|
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C" # Necessary for web-app-matrix
|
||||||
networks:
|
networks:
|
||||||
- name: "{{ applications | get_app_conf(application_id, 'network', True) }}"
|
- name: "{{ postgres_network_name }}"
|
||||||
published_ports:
|
published_ports:
|
||||||
- "127.0.0.1:{{ database_port }}:5432"
|
- "127.0.0.1:{{ postgres_port }}:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- "{{ applications['svc-db-postgres'].volume }}:/var/lib/postgresql/data"
|
- "{{ postgres_volume }}:/var/lib/postgresql/data"
|
||||||
restart_policy: "{{ docker_restart_policy }}"
|
restart_policy: "{{ docker_restart_policy }}"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
when: run_once_docker_postgres is not defined
|
when: run_once_docker_postgres is not defined
|
||||||
|
|
||||||
- name: Wait for Postgres inside the container
|
- name: Wait for Postgres inside the container
|
||||||
shell: "docker exec {{ applications | get_app_conf(application_id, 'hostname', True) }} pg_isready -U postgres"
|
shell: "docker exec {{ postgres_hostname }} pg_isready -U postgres"
|
||||||
register: pg_ready
|
register: pg_ready
|
||||||
until: pg_ready.rc == 0
|
until: pg_ready.rc == 0
|
||||||
retries: 30
|
retries: 30
|
||||||
@@ -47,12 +47,9 @@
|
|||||||
state: present
|
state: present
|
||||||
when: run_once_docker_postgres is not defined
|
when: run_once_docker_postgres is not defined
|
||||||
|
|
||||||
- name: Load database initialization tasks dynamically
|
- name: "Initialize database for '{{ database_name }}'"
|
||||||
include_tasks: init_database.yml
|
include_tasks: init.yml
|
||||||
when:
|
when: "{{ postgres_init }}"
|
||||||
- database_username is defined
|
|
||||||
- database_password is defined
|
|
||||||
- database_name is defined
|
|
||||||
|
|
||||||
- name: Run the docker_postgres tasks once
|
- name: Run the docker_postgres tasks once
|
||||||
set_fact:
|
set_fact:
|
||||||
|
@@ -1 +1,10 @@
|
|||||||
application_id: svc-db-postgres
|
application_id: svc-db-postgres
|
||||||
|
postgres_volume: "{{ applications | get_app_conf(application_id, 'docker.services.postgres.volume', True) }}"
|
||||||
|
postgres_hostname: "{{ applications | get_app_conf(application_id, 'hostname', True) }}"
|
||||||
|
postgres_image: "{{ applications | get_app_conf(application_id, 'docker.services.postgres.image', True) }}"
|
||||||
|
postgres_subnet: "{{ networks.local['svc-db-postgres'].subnet }}"
|
||||||
|
postgres_network_name: "{{ applications | get_app_conf(application_id, 'network', True) }}"
|
||||||
|
postgres_version: "{{ applications | get_app_conf(application_id, 'docker.services.postgres.version', True) }}"
|
||||||
|
postgres_password: "{{ applications | get_app_conf(application_id, 'credentials.postgres_password', True) }}"
|
||||||
|
postgres_port: "{{ database_port | default(ports.localhost.database[ application_id ]) }}"
|
||||||
|
postgres_init: "{{ database_username is defined and database_password is defined and database_name is defined }}"
|
@@ -1,10 +1,8 @@
|
|||||||
images:
|
|
||||||
keycloak: "quay.io/keycloak/keycloak:latest"
|
|
||||||
import_realm: True # If True realm will be imported. If false skip.
|
import_realm: True # If True realm will be imported. If false skip.
|
||||||
features:
|
features:
|
||||||
matomo: true
|
matomo: true
|
||||||
css: false
|
css: false
|
||||||
port-ui-desktop: true
|
port-ui-desktop: true
|
||||||
ldap: true
|
ldap: true
|
||||||
central_database: true
|
central_database: true
|
||||||
recaptcha: true
|
recaptcha: true
|
||||||
@@ -26,6 +24,9 @@ scopes:
|
|||||||
rbac_groups: "/rbac"
|
rbac_groups: "/rbac"
|
||||||
docker:
|
docker:
|
||||||
services:
|
services:
|
||||||
|
keycloak:
|
||||||
|
image: "quay.io/keycloak/keycloak"
|
||||||
|
version: "latest"
|
||||||
database:
|
database:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
@@ -72,11 +72,11 @@
|
|||||||
- name: Render user-profile JSON for SSH key
|
- name: Render user-profile JSON for SSH key
|
||||||
template:
|
template:
|
||||||
src: import/user-profile.json.j2
|
src: import/user-profile.json.j2
|
||||||
dest: "{{ import_directory_host }}/user-profile.json"
|
dest: "{{ keycloak_host_import_directory }}/user-profile.json"
|
||||||
mode: '0644'
|
mode: '0644'
|
||||||
notify: docker compose up
|
notify: docker compose up
|
||||||
|
|
||||||
- name: Apply SSH Public Key to user-profile via kcadm
|
- name: Apply SSH Public Key to user-profile via kcadm
|
||||||
shell: |
|
shell: |
|
||||||
docker exec -i {{ container_name }} \
|
docker exec -i {{ container_name }} \
|
||||||
/opt/keycloak/bin/kcadm.sh update realms/{{ keycloak_realm }} -f {{ import_directory_docker }}user-profile.json
|
/opt/keycloak/bin/kcadm.sh update realms/{{ keycloak_realm }} -f {{ keycloak_docker_import_directory }}user-profile.json
|
||||||
|
@@ -3,16 +3,16 @@
|
|||||||
include_role:
|
include_role:
|
||||||
name: cmp-db-docker-proxy
|
name: cmp-db-docker-proxy
|
||||||
|
|
||||||
- name: "create directory {{import_directory_host}}"
|
- name: "create directory {{keycloak_host_import_directory}}"
|
||||||
file:
|
file:
|
||||||
path: "{{import_directory_host}}"
|
path: "{{keycloak_host_import_directory}}"
|
||||||
state: directory
|
state: directory
|
||||||
mode: 0755
|
mode: 0755
|
||||||
|
|
||||||
- name: "Copy import files to {{ import_directory_host }}"
|
- name: "Copy import files to {{ keycloak_host_import_directory }}"
|
||||||
template:
|
template:
|
||||||
src: "{{ item }}"
|
src: "{{ item }}"
|
||||||
dest: "{{ import_directory_host }}/{{ item | basename | regex_replace('\\.j2$', '') }}"
|
dest: "{{ keycloak_host_import_directory }}/{{ item | basename | regex_replace('\\.j2$', '') }}"
|
||||||
mode: '770'
|
mode: '770'
|
||||||
loop: "{{ lookup('fileglob', '{{ role_path }}/templates/import/*.j2', wantlist=True) }}"
|
loop: "{{ lookup('fileglob', '{{ role_path }}/templates/import/*.j2', wantlist=True) }}"
|
||||||
notify: docker compose up
|
notify: docker compose up
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
{% include 'roles/docker-compose/templates/base.yml.j2' %}
|
{% include 'roles/docker-compose/templates/base.yml.j2' %}
|
||||||
|
|
||||||
application:
|
application:
|
||||||
image: "{{ applications | get_app_conf(application_id, 'images.keycloak', True) }}"
|
image: "{{ keycloak_image }}:{{ keycloak_version }}"
|
||||||
container_name: {{container_name}}
|
container_name: {{ keycloak_container }}
|
||||||
command: start {% if applications | get_app_conf(application_id, 'import_realm', True) | bool %}--import-realm{% endif %}
|
command: start{% if keycloak_import_realm %} --import-realm{% endif %}{% if keycloak_debug_enabled %} --verbose{% endif %}
|
||||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||||
ports:
|
ports:
|
||||||
- "{{ keycloak_server_host }}:8080"
|
- "{{ keycloak_server_host }}:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- "{{import_directory_host}}:{{import_directory_docker}}"
|
- "{{keycloak_host_import_directory}}:{{keycloak_docker_import_directory}}"
|
||||||
{% include 'roles/docker-container/templates/depends_on/dmbs_excl.yml.j2' %}
|
{% include 'roles/docker-container/templates/depends_on/dmbs_excl.yml.j2' %}
|
||||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||||
{% set container_port = 9000 %}
|
{% set container_port = 9000 %}
|
||||||
|
@@ -1,14 +1,17 @@
|
|||||||
application_id: "keycloak"
|
application_id: "keycloak" # Internal CyMaIS application id
|
||||||
database_type: "postgres"
|
database_type: "postgres" # Database which will be used
|
||||||
container_name: "{{application_id}}_application"
|
keycloak_container: "{{ application_id }}_application" # Name of the keycloack docker container
|
||||||
import_directory_host: "{{docker_compose.directories.volumes}}import/" # Directory in which keycloack import files are placed on the host
|
keycloak_host_import_directory: "{{ docker_compose.directories.volumes }}import/" # Directory in which keycloack import files are placed on the host
|
||||||
import_directory_docker: "/opt/keycloak/data/import/" # Directory in which keycloack import files are placed in the running docker container
|
keycloak_docker_import_directory: "/opt/keycloak/data/import/" # Directory in which keycloack import files are placed in the running docker container
|
||||||
keycloak_realm: "{{ primary_domain}}" # This is the name of the default realm which is used by the applications
|
keycloak_realm: "{{ primary_domain}}" # This is the name of the default realm which is used by the applications
|
||||||
keycloak_administrator: "{{ applications | get_app_conf(application_id, 'users.administrator', True) }}" # Master Administrator
|
keycloak_administrator: "{{ applications | get_app_conf(application_id, 'users.administrator', True) }}" # Master Administrator
|
||||||
keycloak_administrator_username: "{{ keycloak_administrator.username}}" # Master Administrator Username
|
keycloak_administrator_username: "{{ keycloak_administrator.username }}" # Master Administrator Username
|
||||||
keycloak_administrator_password: "{{ keycloak_administrator.password}}" # Master Administrator Password
|
keycloak_administrator_password: "{{ keycloak_administrator.password }}" # Master Administrator Password
|
||||||
keycloak_kcadm_path: "docker exec -i {{ container_name }} /opt/keycloak/bin/kcadm.sh"
|
keycloak_kcadm_path: "docker exec -i {{ keycloak_container }} /opt/keycloak/bin/kcadm.sh" # Init script for keycloak
|
||||||
keycloak_server_internal_url: "http://127.0.0.1:8080"
|
keycloak_server_internal_url: "http://127.0.0.1:8080"
|
||||||
keycloak_server_host: "127.0.0.1:{{ports.localhost.http[application_id]}}"
|
keycloak_server_host: "127.0.0.1:{{ ports.localhost.http[application_id] }}"
|
||||||
keycloak_server_host_url: "http://{{ keycloak_server_host }}"
|
keycloak_server_host_url: "http://{{ keycloak_server_host }}"
|
||||||
|
keycloak_image: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.image', True) }}" # Keycloak docker image
|
||||||
|
keycloak_version: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.version', True) }}" # Keyloak docker version
|
||||||
|
keycloak_import_realm: "{{ applications | get_app_conf(application_id, 'import_realm', True, True) }}" # Activate realm import
|
||||||
|
keycloak_debug_enabled: "{{ enable_debug }}"
|
@@ -33,12 +33,12 @@ class TestDeprecatedVersionKey(unittest.TestCase):
|
|||||||
if uses_version:
|
if uses_version:
|
||||||
warnings.append(
|
warnings.append(
|
||||||
f"[DEPRECATION WARNING] {role_path.name}/config/main.yml: "
|
f"[DEPRECATION WARNING] {role_path.name}/config/main.yml: "
|
||||||
f"'version' is deprecated. Replace it by docker.versions[version]."
|
f"'version' is deprecated. Replace it by docker.services[service].version."
|
||||||
)
|
)
|
||||||
if uses_images:
|
if uses_images:
|
||||||
warnings.append(
|
warnings.append(
|
||||||
f"[DEPRECATION WARNING] {role_path.name}/config/main.yml: "
|
f"[DEPRECATION WARNING] {role_path.name}/config/main.yml: "
|
||||||
f"'images' is deprecated. Replace it by docker.images[image]."
|
f"'images' is deprecated. Replace it by docker.services[service].image."
|
||||||
)
|
)
|
||||||
|
|
||||||
if warnings:
|
if warnings:
|
||||||
|
@@ -3,14 +3,11 @@ import yaml
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
|
|
||||||
class TestDockerRoleImagesConfiguration(unittest.TestCase):
|
class TestDockerRoleServicesConfiguration(unittest.TestCase):
|
||||||
def test_images_keys_and_templates(self):
|
def test_services_keys_and_templates(self):
|
||||||
"""
|
"""
|
||||||
For each web-app-* role, check that:
|
For each web-app-* role, check that:
|
||||||
- roles/web-app-*/config/main.yml contains 'images' as a dict with keys/values
|
- roles/web-app-*/config/main.yml contains 'services' as a dict with keys/values
|
||||||
- Each image key is referenced as:
|
|
||||||
image: "{{ applications[application_id].images.<key> }}"
|
|
||||||
in either roles/web-app-*/templates/docker-compose.yml.j2 or env.j2
|
|
||||||
"""
|
"""
|
||||||
repo_root = Path(__file__).resolve().parent.parent.parent
|
repo_root = Path(__file__).resolve().parent.parent.parent
|
||||||
roles_dir = repo_root / "roles"
|
roles_dir = repo_root / "roles"
|
||||||
@@ -33,13 +30,13 @@ class TestDockerRoleImagesConfiguration(unittest.TestCase):
|
|||||||
errors.append(f"{role_path.name}: YAML parse error: {e}")
|
errors.append(f"{role_path.name}: YAML parse error: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
images = config.get("docker",{}).get("images")
|
services = config.get("docker",{}).get("services",{})
|
||||||
if not images:
|
if not services:
|
||||||
warnings.append(f"[WARNING] {role_path.name}: No 'docker.images' key in config/main.yml")
|
warnings.append(f"[WARNING] {role_path.name}: No 'docker.services' key in config/main.yml")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not isinstance(images, dict):
|
if not isinstance(services, dict):
|
||||||
errors.append(f"{role_path.name}: 'images' must be a dict in config/main.yml")
|
errors.append(f"{role_path.name}: 'services' must be a dict in config/main.yml")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# OPTIONAL: Check if the image is available locally via docker images
|
# OPTIONAL: Check if the image is available locally via docker images
|
||||||
@@ -55,9 +52,9 @@ class TestDockerRoleImagesConfiguration(unittest.TestCase):
|
|||||||
# except Exception as e:
|
# except Exception as e:
|
||||||
# errors.append(f"{role_path.name}: Error running 'docker images' (optional): {e}")
|
# errors.append(f"{role_path.name}: Error running 'docker images' (optional): {e}")
|
||||||
if warnings:
|
if warnings:
|
||||||
print("\nWarnings in docker role images configuration:\n" + "\n".join(warnings))
|
print("\nWarnings in docker role services configuration:\n" + "\n".join(warnings))
|
||||||
if errors:
|
if errors:
|
||||||
self.fail("Errors in docker role images configuration:\n" + "\n".join(errors))
|
self.fail("Errors in docker role services configuration:\n" + "\n".join(errors))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
116
tests/unit/utils/test_valid_deploy_id.py
Normal file
116
tests/unit/utils/test_valid_deploy_id.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# File: tests/unit/utils/test_valid_deploy_id.py
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
import yaml
|
||||||
|
from utils.valid_deploy_id import ValidDeployId
|
||||||
|
|
||||||
|
class TestValidDeployId(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# Create a temporary directory for roles
|
||||||
|
self.temp_dir = tempfile.TemporaryDirectory()
|
||||||
|
self.roles_dir = os.path.join(self.temp_dir.name, 'roles')
|
||||||
|
os.makedirs(self.roles_dir)
|
||||||
|
|
||||||
|
# Create a dummy role with application_id 'app1'
|
||||||
|
role_path = os.path.join(self.roles_dir, 'role1', 'vars')
|
||||||
|
os.makedirs(role_path)
|
||||||
|
with open(os.path.join(role_path, 'main.yml'), 'w', encoding='utf-8') as f:
|
||||||
|
yaml.safe_dump({'application_id': 'app1'}, f)
|
||||||
|
|
||||||
|
# Initialize validator with our temp roles_dir
|
||||||
|
self.validator = ValidDeployId(roles_dir=self.roles_dir)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.temp_dir.cleanup()
|
||||||
|
|
||||||
|
def _write_ini_inventory(self, content):
|
||||||
|
fd, path = tempfile.mkstemp(suffix='.ini')
|
||||||
|
os.close(fd)
|
||||||
|
with open(path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def _write_yaml_inventory(self, data):
|
||||||
|
fd, path = tempfile.mkstemp(suffix='.yml')
|
||||||
|
os.close(fd)
|
||||||
|
with open(path, 'w', encoding='utf-8') as f:
|
||||||
|
yaml.safe_dump(data, f)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def test_valid_in_roles_and_ini_inventory(self):
|
||||||
|
# Inventory contains app1 as a host
|
||||||
|
ini_content = """
|
||||||
|
[servers]
|
||||||
|
app1,otherhost
|
||||||
|
"""
|
||||||
|
inv = self._write_ini_inventory(ini_content)
|
||||||
|
result = self.validator.validate(inv, ['app1'])
|
||||||
|
self.assertEqual(result, {}, "app1 should be valid when in roles and ini inventory")
|
||||||
|
|
||||||
|
def test_missing_in_roles(self):
|
||||||
|
# Inventory contains app2 but roles only have app1
|
||||||
|
ini_content = """
|
||||||
|
[servers]
|
||||||
|
app2
|
||||||
|
"""
|
||||||
|
inv = self._write_ini_inventory(ini_content)
|
||||||
|
result = self.validator.validate(inv, ['app2'])
|
||||||
|
# app2 not in roles, but in inventory
|
||||||
|
expected = {'app2': {'in_roles': False, 'in_inventory': True}}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_missing_in_inventory_ini(self):
|
||||||
|
# Roles have app1 but inventory does not mention it
|
||||||
|
ini_content = """
|
||||||
|
[servers]
|
||||||
|
otherhost
|
||||||
|
"""
|
||||||
|
inv = self._write_ini_inventory(ini_content)
|
||||||
|
result = self.validator.validate(inv, ['app1'])
|
||||||
|
expected = {'app1': {'in_roles': True, 'in_inventory': False}}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_missing_both_ini(self):
|
||||||
|
# Neither roles nor inventory have appX
|
||||||
|
ini_content = """
|
||||||
|
[servers]
|
||||||
|
otherhost
|
||||||
|
"""
|
||||||
|
inv = self._write_ini_inventory(ini_content)
|
||||||
|
result = self.validator.validate(inv, ['appX'])
|
||||||
|
expected = {'appX': {'in_roles': False, 'in_inventory': False}}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_valid_in_roles_and_yaml_inventory(self):
|
||||||
|
# YAML inventory with app1 as a dict key
|
||||||
|
data = {'app1': {'hosts': ['app1']}, 'group': {'app1': {}}}
|
||||||
|
inv = self._write_yaml_inventory(data)
|
||||||
|
result = self.validator.validate(inv, ['app1'])
|
||||||
|
self.assertEqual(result, {}, "app1 should be valid in roles and yaml inventory")
|
||||||
|
|
||||||
|
def test_missing_in_roles_yaml(self):
|
||||||
|
# YAML inventory has app2 key but roles only have app1
|
||||||
|
data = {'app2': {}}
|
||||||
|
inv = self._write_yaml_inventory(data)
|
||||||
|
result = self.validator.validate(inv, ['app2'])
|
||||||
|
expected = {'app2': {'in_roles': False, 'in_inventory': True}}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_missing_in_inventory_yaml(self):
|
||||||
|
# Roles have app1 but YAML inventory has no app1
|
||||||
|
data = {'group': {'other': {}}}
|
||||||
|
inv = self._write_yaml_inventory(data)
|
||||||
|
result = self.validator.validate(inv, ['app1'])
|
||||||
|
expected = {'app1': {'in_roles': True, 'in_inventory': False}}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_missing_both_yaml(self):
|
||||||
|
data = {}
|
||||||
|
inv = self._write_yaml_inventory(data)
|
||||||
|
result = self.validator.validate(inv, ['unknown'])
|
||||||
|
expected = {'unknown': {'in_roles': False, 'in_inventory': False}}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
89
utils/valid_deploy_id.py
Normal file
89
utils/valid_deploy_id.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# File: utils/valid_deploy_id.py
|
||||||
|
"""
|
||||||
|
Utility for validating deployment application IDs against defined roles and inventory.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
import glob
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
from filter_plugins.get_all_application_ids import get_all_application_ids
|
||||||
|
|
||||||
|
class ValidDeployId:
|
||||||
|
def __init__(self, roles_dir='roles'):
|
||||||
|
# Load all known application IDs from roles
|
||||||
|
self.valid_ids = set(get_all_application_ids(roles_dir))
|
||||||
|
|
||||||
|
def validate(self, inventory_path, ids):
|
||||||
|
"""
|
||||||
|
Validate a list of application IDs against both role definitions and inventory.
|
||||||
|
Returns a dict mapping invalid IDs to their presence status.
|
||||||
|
Example:
|
||||||
|
{
|
||||||
|
"app1": {"in_roles": False, "in_inventory": True},
|
||||||
|
"app2": {"in_roles": True, "in_inventory": False}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
invalid = {}
|
||||||
|
for app_id in ids:
|
||||||
|
in_roles = app_id in self.valid_ids
|
||||||
|
in_inventory = self._exists_in_inventory(inventory_path, app_id)
|
||||||
|
if not (in_roles and in_inventory):
|
||||||
|
invalid[app_id] = {
|
||||||
|
'in_roles': in_roles,
|
||||||
|
'in_inventory': in_inventory
|
||||||
|
}
|
||||||
|
return invalid
|
||||||
|
|
||||||
|
def _exists_in_inventory(self, inventory_path, app_id):
|
||||||
|
_, ext = os.path.splitext(inventory_path)
|
||||||
|
if ext in ('.yml', '.yaml'):
|
||||||
|
return self._search_yaml_keys(inventory_path, app_id)
|
||||||
|
else:
|
||||||
|
return self._search_ini_sections(inventory_path, app_id)
|
||||||
|
|
||||||
|
def _search_ini_sections(self, inventory_path, app_id):
|
||||||
|
"""
|
||||||
|
Manually parse INI inventory for sections and host lists.
|
||||||
|
Returns True if app_id matches a section name or a host in a section.
|
||||||
|
"""
|
||||||
|
present = False
|
||||||
|
with open(inventory_path, 'r', encoding='utf-8') as f:
|
||||||
|
current_section = None
|
||||||
|
for raw in f:
|
||||||
|
line = raw.strip()
|
||||||
|
# Skip blanks and comments
|
||||||
|
if not line or line.startswith(('#', ';')):
|
||||||
|
continue
|
||||||
|
# Section header
|
||||||
|
if line.startswith('[') and line.endswith(']'):
|
||||||
|
current_section = line[1:-1].strip()
|
||||||
|
if current_section == app_id:
|
||||||
|
return True
|
||||||
|
continue
|
||||||
|
# Host or variable line under a section
|
||||||
|
if current_section:
|
||||||
|
# Split on commas or whitespace
|
||||||
|
for part in [p.strip() for p in line.replace(',', ' ').split()]:
|
||||||
|
if part == app_id:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _search_yaml_keys(self, inventory_path, app_id):
|
||||||
|
with open(inventory_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = yaml.safe_load(f)
|
||||||
|
return self._find_key(data, app_id)
|
||||||
|
|
||||||
|
def _find_key(self, node, key): # recursive search
|
||||||
|
if isinstance(node, dict):
|
||||||
|
for k, v in node.items():
|
||||||
|
# If key matches and maps to a dict or list, consider it present
|
||||||
|
if k == key and isinstance(v, (dict, list)):
|
||||||
|
return True
|
||||||
|
if self._find_key(v, key):
|
||||||
|
return True
|
||||||
|
elif isinstance(node, list):
|
||||||
|
for item in node:
|
||||||
|
if self._find_key(item, key):
|
||||||
|
return True
|
||||||
|
return False
|
Reference in New Issue
Block a user