From 82d438c34a0f01d7b157d6e22931f4811af2c0f2 Mon Sep 17 00:00:00 2001 From: Murat Aybars <39916128+aybarsm@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:11:59 +0100 Subject: [PATCH] Proxmox role --- roles/auth/defaults/main.yml | 3 + roles/auth/tasks/authorized_keys.yml | 21 +-- roles/auth/tasks/ssh.yml | 6 + roles/auth/tasks/users.yml | 1 + roles/auth/vars/main.yml | 106 ++------------- roles/network/vars/main.yml | 11 +- roles/proxmox/tasks/auth_key_config.yml | 52 +++++++ roles/proxmox/tasks/cluster_acls.yml | 11 -- roles/proxmox/tasks/cluster_groups.yml | 9 -- roles/proxmox/tasks/cluster_pools.yml | 9 -- roles/proxmox/tasks/cluster_roles.yml | 9 -- roles/proxmox/tasks/cluster_users.yml | 16 --- roles/proxmox/tasks/datacenter.yml | 58 ++++++++ roles/proxmox/tasks/datacenter/acl.yml | 9 ++ .../config.yml} | 0 roles/proxmox/tasks/datacenter/group.yml | 7 + roles/proxmox/tasks/datacenter/pool.yml | 7 + roles/proxmox/tasks/datacenter/role.yml | 7 + roles/proxmox/tasks/datacenter/user.yml | 14 ++ roles/proxmox/tasks/main.yml | 127 ++++++++++++++++-- roles/proxmox/tasks/qemu/disk.yml | 36 ++++- roles/proxmox/tasks/qemu/vm.yml | 4 +- roles/proxmox/tasks/ssl_config.yml | 30 +++++ roles/proxmox/vars/main.yml | 13 +- 24 files changed, 390 insertions(+), 176 deletions(-) create mode 100644 roles/proxmox/tasks/auth_key_config.yml delete mode 100644 roles/proxmox/tasks/cluster_acls.yml delete mode 100644 roles/proxmox/tasks/cluster_groups.yml delete mode 100644 roles/proxmox/tasks/cluster_pools.yml delete mode 100644 roles/proxmox/tasks/cluster_roles.yml delete mode 100644 roles/proxmox/tasks/cluster_users.yml create mode 100644 roles/proxmox/tasks/datacenter.yml create mode 100644 roles/proxmox/tasks/datacenter/acl.yml rename roles/proxmox/tasks/{cluster_config.yml => datacenter/config.yml} (100%) create mode 100644 roles/proxmox/tasks/datacenter/group.yml create mode 100644 roles/proxmox/tasks/datacenter/pool.yml create mode 100644 roles/proxmox/tasks/datacenter/role.yml create mode 100644 roles/proxmox/tasks/datacenter/user.yml create mode 100644 roles/proxmox/tasks/ssl_config.yml diff --git a/roles/auth/defaults/main.yml b/roles/auth/defaults/main.yml index dd9accd..ae692ae 100644 --- a/roles/auth/defaults/main.yml +++ b/roles/auth/defaults/main.yml @@ -1,5 +1,8 @@ auth__role_enabled: false +auth__no_log: true +auth__use_only: ['host', 'group', 'default'] + auth__manage_groups: false auth__manage_users: false auth__manage_authorized_keys: false diff --git a/roles/auth/tasks/authorized_keys.yml b/roles/auth/tasks/authorized_keys.yml index 1634c2f..81520b6 100644 --- a/roles/auth/tasks/authorized_keys.yml +++ b/roles/auth/tasks/authorized_keys.yml @@ -1,6 +1,6 @@ --- - name: Set fact for ssh key distribution to authorized keys, if any - set_fact: + ansible.builtin.set_fact: auth__authorized_keys_all: "{{ (auth__authorized_keys_all | default([])) + ssh_keys_distribute }}" vars: ssh_keys_distribute: "{{ dict(hostvars) | @@ -19,22 +19,23 @@ register: auth__authorized_keys_test_user_existence when: - auth__authorized_keys_all | length > 0 - - ansible_check_mode | bool + - ansible_check_mode - name: Apply authorized keys configuration become: true ansible.posix.authorized_key: - comment: "{{ item.comment | default(omit) }}" - exclusive: "{{ item.exclusive | default(omit) }}" - follow: "{{ item.follow | default(omit) }}" + comment: "{{ item.comment | default(omit, true) }}" + exclusive: "{{ item.exclusive | default(omit, true) }}" + follow: "{{ item.follow | default(omit, true) }}" key: "{{ item.key }}" - key_options: "{{ item.key_options | default(omit) }}" - manage_dir: "{{ item.manage_dir | default(omit) }}" - path: "{{ item.path | default(omit) }}" - state: "{{ item.state | default(omit) }}" + key_options: "{{ item.key_options | default(omit, true) }}" + manage_dir: "{{ item.manage_dir | default(omit, true) }}" + path: "{{ item.path | default(omit, true) }}" + state: "{{ item.state | default(omit, true) }}" user: "{{ item.user }}" - validate_certs: "{{ item.validate_certs | default(omit) }}" + validate_certs: "{{ item.validate_certs | default(omit, true) }}" loop: "{{ auth__authorized_keys_all }}" + no_log: "{{ auth__no_log | default(true, true) | bool }}" register: auth__authorized_keys_apply when: - auth__authorized_keys_all | length > 0 diff --git a/roles/auth/tasks/ssh.yml b/roles/auth/tasks/ssh.yml index 0e79767..a5a5cb8 100644 --- a/roles/auth/tasks/ssh.yml +++ b/roles/auth/tasks/ssh.yml @@ -30,6 +30,9 @@ dest: "{{ auth__sshd_config_file }}" backup: "{{ auth__sshd_config_backup | default(omit) | bool }}" validate: "{{ auth__sshd_validate | default(omit) }}" + mode: "{{ auth__sshd_config_file_module.mode | default(omit, true) }}" + owner: "{{ auth__sshd_config_file_module.owner | default(omit, true) }}" + group: "{{ auth__sshd_config_file_module.group | default(omit, true) }}" register: auth__sshd_config_apply notify: "auth__ssh_apply_changes" when: auth__sshd_config_all | default([]) | length > 0 @@ -64,6 +67,9 @@ src: "{{ auth__ssh_config_template }}" dest: "{{ auth__ssh_config_file }}" backup: "{{ auth__ssh_config_backup | default(omit) | bool }}" + mode: "{{ auth__ssh_config_file_module.mode | default(omit, true) }}" + owner: "{{ auth__ssh_config_file_module.owner | default(omit, true) }}" + group: "{{ auth__ssh_config_file_module.group | default(omit, true) }}" register: auth__ssh_config_apply notify: "auth__ssh_apply_changes" when: auth__ssh_config_all | default([]) | length > 0 diff --git a/roles/auth/tasks/users.yml b/roles/auth/tasks/users.yml index b4cfe5c..359c5dc 100644 --- a/roles/auth/tasks/users.yml +++ b/roles/auth/tasks/users.yml @@ -40,5 +40,6 @@ umask: "{{ item.umask | default(omit) }}" update_password: "{{ item.update_password | default(omit) }}" loop: "{{ auth__users_all }}" + no_log: "{{ auth__no_log | default(true, '') | bool }}" register: auth__users_apply when: auth__users_all | default([]) | length > 0 diff --git a/roles/auth/vars/main.yml b/roles/auth/vars/main.yml index 988eede..a08f8d9 100644 --- a/roles/auth/vars/main.yml +++ b/roles/auth/vars/main.yml @@ -1,105 +1,15 @@ --- -auth__all: "{{ (auth__host + auth__group + auth__default) | selectattr('type', 'defined') }}" +auth__all: "{{ {'host': auth__host, 'group': auth__group, 'default': auth__default} | aybarsm.helper.role_vars(only=auth__use_only) }}" -__auth__config: - groups: - selectattr: - - when: [['type', 'eq', 'group']] - - when: "{{ __ansible.modules.ansible_builtin_group.uniques | product(['defined']) | list }}" - logic: or - authorized_keys: - selectattr: - - when: - - ['type', 'defined'] - - ['type', 'equalto', 'authorized_key'] - - ['user', 'defined'] - - ['key', 'defined'] - users: - selectattr: - - when: - - ['type', 'defined'] - - ['type', 'equalto', 'user'] - - when: "{{ __ansible.modules.ansible_builtin_user.uniques | product(['defined']) | list }}" - logic: or - sshd_config: - selectattr: - - when: - - ['type', 'defined'] - - ['type', 'equalto', 'sshd_config'] - - ['name', 'defined'] - - ['value', 'defined'] - ssh_config: - selectattr: - - when: - - ['type', 'defined'] - - ['type', 'equalto', 'ssh_config'] - - ['name', 'defined'] - - ['value', 'defined'] - ssh_config_module: - selectattr: - - when: - - ['type', 'defined'] - - ['type', 'equalto', 'ssh_config_module'] - - ['host', 'defined'] - sudoers_file: - selectattr: - - when: - - ['type', 'defined'] - - ['type', 'equalto', 'sudoers_file'] - - ['entry', 'defined'] - sudoers_module: - selectattr: - - when: - - ['type', 'defined'] - - ['type', 'equalto', 'sudoers_module'] - - ['name', 'defined'] +auth__users_all: "{{ auth__all | selectattr('entry__type', 'eq', 'user') }}" -auth__groups_all: "{{ auth__all | - aybarsm.helper.selectattr(__auth__config.groups.selectattr) | - aybarsm.helper.unique_recursive(__ansible.modules.ansible_builtin_group.uniques) | - default([]) }}" +auth__authorized_keys_all: "{{ auth__all | selectattr('entry__type', 'eq', 'authorized_key') }}" -# Use lists_mergeby to combine all sections and sort by name for better output readability -auth__users_all: "{{ [auth__default, auth__group, auth__host] | - map('selectattr', 'type', 'defined') | map('selectattr', 'type', 'equalto', 'user') | - map('aybarsm.helper.replace_aliases', __ansible.modules.ansible_builtin_user.aliases) | - map('selectattr', 'name', 'defined') | - community.general.lists_mergeby('name', recursive=false, list_merge='prepend') | - sort(attribute='name') | default([]) }}" +auth__sshd_config_all: "{{ auth__all | selectattr('entry__type', 'eq', 'sshd_config') | + sort(attribute='value', reverse=true) | sort(attribute='name', reverse=false) }}" -auth__authorized_keys_all: "{{ auth__all | - aybarsm.helper.selectattr(__auth__config.authorized_keys.selectattr) | - aybarsm.helper.unique_combinations([['user', 'key']]) | - default([]) }}" - -# Sort name and value to avoid unneccessary changes -auth__sshd_config_all: "{{ auth__all | - aybarsm.helper.selectattr(__auth__config.sshd_config.selectattr) | - aybarsm.helper.unique_combinations([['name', 'value']]) | - sort(attribute='value', reverse=true) | sort(attribute='name', reverse=false) | - default([]) }}" - -# Sort name and value to avoid unneccessary changes -auth__ssh_config_all: "{{ auth__all | - aybarsm.helper.selectattr(__auth__config.ssh_config.selectattr) | - aybarsm.helper.unique_combinations([['name', 'value']]) | - sort(attribute='value', reverse=true) | sort(attribute='name', reverse=false) | - default([]) }}" - -auth__ssh_config_module_all: "{{ auth__all | - aybarsm.helper.selectattr(__auth__config.ssh_config_module.selectattr) | - aybarsm.helper.unique_recursive('host') | - default([]) }}" - -auth__sudoers_file_all: "{{ auth__all | - aybarsm.helper.selectattr(__auth__config.sudoers_file.selectattr) | - aybarsm.helper.unique_recursive('entry') | - default([]) }}" - -auth__sudoers_module_all: "{{ auth__all | - aybarsm.helper.selectattr(__auth__config.sudoers_module.selectattr) | - aybarsm.helper.unique_recursive('name') | - default([]) }}" +auth__ssh_config_all: "{{ auth__all | selectattr('entry__type', 'eq', 'ssh_config') | + sort(attribute='value', reverse=true) | sort(attribute='name', reverse=false) }}" __auth__key_distribute_query: "*.auth__users_apply.results[*] | [] | - [?contains(not_null(item.distribute_ssh_key,``),`{{ inventory_hostname }}`)].{user: name, key: ssh_public_key}" \ No newline at end of file + [?contains(not_null(item.entry__distribute_ssh_key,``),`{{ inventory_hostname }}`)].{user: name, key: ssh_public_key}" \ No newline at end of file diff --git a/roles/network/vars/main.yml b/roles/network/vars/main.yml index 8e6acbf..6d27da7 100644 --- a/roles/network/vars/main.yml +++ b/roles/network/vars/main.yml @@ -5,4 +5,13 @@ network__sysctl_all: "{{ network__all | selectattr('entry__type', 'eq', 'sysctl' aybarsm.helper.replace_aliases(__ansible.modules.ansible_posix_sysctl.aliases, removeAliases=true) | community.general.json_query('[?not_null(name) && not_null(value)]') | unique(attribute='name') }}" -##### END: network sysctl vars \ No newline at end of file +##### END: network sysctl vars + +##### BEGIN: network hosts vars +network__hosts_all: "{{ network__all | selectattr('entry__type', 'eq', 'host') | + aybarsm.helper.replace_aliases({'fqdn': ['hostname']}) | unique(attribute='ip') }}" + +# Sort hosts by hostname to avoid unneccessary changes +network__hosts_all_ipv4: "{{ network__hosts_all | selectattr('ip', 'ansible.utils.ipv4') | sort(attribute='hostname') }}" +network__hosts_all_ipv6: "{{ network__hosts_all | selectattr('ip', 'ansible.utils.ipv6') | sort(attribute='hostname') }}" +##### END: network hosts vars \ No newline at end of file diff --git a/roles/proxmox/tasks/auth_key_config.yml b/roles/proxmox/tasks/auth_key_config.yml new file mode 100644 index 0000000..1078432 --- /dev/null +++ b/roles/proxmox/tasks/auth_key_config.yml @@ -0,0 +1,52 @@ +- name: Check proxmox authorized_keys exists + ansible.builtin.stat: + path: /etc/pve/priv/authorized_keys + register: proxmox__stat_proxmox_auth_keys + +- name: Assert proxmox authorized_keys exists + ansible.builtin.assert: + that: + - proxmox__stat_proxmox_auth_keys.stat.exists + fail_msg: "Proxmox authorized_keys does not exist. This problem must be fixed." + success_msg: "Proxmox authorized_keys exists" + register: proxmox__assert_proxmox_auth_keys + +- name: Check Proxmox authorized_keys linked to root + ansible.builtin.stat: + path: /root/.ssh/authorized_keys + register: proxmox__stat_root_auth_keys + when: proxmox__assert_proxmox_auth_keys is success + +- name: Adjust root authorized_keys when not linked to Proxmox + block: + - name: Retrieve root authorized_keys content + ansible.builtin.slurp: + src: /root/.ssh/authorized_keys + register: proxmox__slurp_root_auth_keys + + - name: Remove root authorized_keys file + ansible.builtin.file: + path: /root/.ssh/authorized_keys + state: absent + register: proxmox__remove_root_auth_keys + + - name: Move root authorized_keys content to Proxmox + ansible.builtin.copy: + content: "{{ proxmox__slurp_root_auth_keys.content | b64decode }}" + dest: /etc/pve/priv/authorized_keys + mode: '0600' + owner: root + group: 'www-data' + register: proxmox__move_root_auth_keys_content + + - name: Link Proxmox authorized_keys to root + ansible.builtin.file: + src: /etc/pve/priv/authorized_keys + dest: /root/.ssh/authorized_keys + state: link + register: proxmox__link_proxmox_auth_keys + + when: + - proxmox__assert_proxmox_auth_keys is success + - (not proxmox__stat_root_auth_keys.stat.islnk) or (proxmox__stat_root_auth_keys.stat.lnk_source != '/etc/pve/priv/authorized_keys') + diff --git a/roles/proxmox/tasks/cluster_acls.yml b/roles/proxmox/tasks/cluster_acls.yml deleted file mode 100644 index 09e232d..0000000 --- a/roles/proxmox/tasks/cluster_acls.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -- name: Deploy Proxmox cluster ACLs - aybarsm.linux.proxmox_acl: - path: "{{ item.path }}" - roles: "{{ item.roles }}" - state: "{{ item.state | default('present') }}" - groups: "{{ item.groups | default([]) }}" - users: "{{ item.users | default([]) }}" - with_items: "{{ __proxmox__cluster.acls }}" - when: __proxmox__cluster.acls | default([]) | length > 0 - register: proxmox__cluster_acls_deploy \ No newline at end of file diff --git a/roles/proxmox/tasks/cluster_groups.yml b/roles/proxmox/tasks/cluster_groups.yml deleted file mode 100644 index 45074d7..0000000 --- a/roles/proxmox/tasks/cluster_groups.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -- name: Deploy Proxmox cluster groups - aybarsm.linux.proxmox_group: - name: "{{ item.name }}" - state: "{{ item.state | default('present') }}" - comment: "{{ item.comment | default(omit) }}" - with_items: "{{ __proxmox__cluster.groups }}" - when: __proxmox__cluster.groups | default([]) | length > 0 - register: proxmox__cluster_groups_deploy \ No newline at end of file diff --git a/roles/proxmox/tasks/cluster_pools.yml b/roles/proxmox/tasks/cluster_pools.yml deleted file mode 100644 index b2a68b3..0000000 --- a/roles/proxmox/tasks/cluster_pools.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -- name: Deploy Proxmox cluster pools - aybarsm.linux.proxmox_pool: - name: "{{ item.name }}" - state: "{{ item.state | default('present') }}" - comment: "{{ item.comment | default(omit) }}" - with_items: "{{ __proxmox__cluster.pools }}" - when: __proxmox__cluster.pools | default([]) | length > 0 - register: proxmox__cluster_pools_deploy \ No newline at end of file diff --git a/roles/proxmox/tasks/cluster_roles.yml b/roles/proxmox/tasks/cluster_roles.yml deleted file mode 100644 index 67fd77e..0000000 --- a/roles/proxmox/tasks/cluster_roles.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -- name: Deploy Proxmox cluster roles - aybarsm.linux.proxmox_role: - name: "{{ item.name }}" - privileges: "{{ item.privileges }}" - state: "{{ item.state | default('present') }}" - with_items: "{{ __proxmox__cluster.roles }}" - when: __proxmox__cluster.roles | default([]) | length > 0 - register: proxmox__cluster_roles_deploy \ No newline at end of file diff --git a/roles/proxmox/tasks/cluster_users.yml b/roles/proxmox/tasks/cluster_users.yml deleted file mode 100644 index a555eef..0000000 --- a/roles/proxmox/tasks/cluster_users.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- name: Deploy Proxmox cluster users - aybarsm.linux.proxmox_user: - name: "{{ item.name }}" - state: "{{ item.state | default('present') }}" - enable: "{{ item.enable | default(omit) }}" - groups: "{{ item.groups | default([]) }}" - comment: "{{ item.comment | default(omit) }}" - email: "{{ item.email | default(omit) }}" - firstname: "{{ item.firstname | default(omit) }}" - lastname: "{{ item.lastname | default(omit) }}" - password: "{{ item.password | default(omit) }}" - expire: "{{ item.expire | default(omit) }}" - with_items: "{{ __proxmox__cluster.users }}" - when: __proxmox__cluster.users | default([]) | length > 0 - register: proxmox__cluster_users_deploy \ No newline at end of file diff --git a/roles/proxmox/tasks/datacenter.yml b/roles/proxmox/tasks/datacenter.yml new file mode 100644 index 0000000..fad9708 --- /dev/null +++ b/roles/proxmox/tasks/datacenter.yml @@ -0,0 +1,58 @@ +--- +- name: Include Proxmox datacenter pool tasks + ansible.builtin.include_tasks: + file: datacenter/pool.yml + loop: "{{ __proxmox__cluster.pools }}" + loop_control: + loop_var: proxmox__dc_pool + index_var: proxmox__dc_pool_index + when: + - __proxmox__cluster.manage_pools | default(false) | bool + - __proxmox__cluster.pools | default([]) | length > 0 + +- name: Include Proxmox datacenter role tasks + ansible.builtin.include_tasks: + file: datacenter/role.yml + loop: "{{ __proxmox__cluster.roles }}" + loop_control: + loop_var: proxmox__dc_role + index_var: proxmox__dc_role_index + when: + - __proxmox__cluster.manage_roles | default(false) | bool + - __proxmox__cluster.roles | default([]) | length > 0 + +- name: Include Proxmox datacenter user group tasks + ansible.builtin.include_tasks: + file: datacenter/group.yml + loop: "{{ __proxmox__cluster.groups }}" + loop_control: + loop_var: proxmox__dc_group + index_var: proxmox__dc_group_index + when: + - proxmox__role_enabled + - __proxmox__cluster.manage_groups | default(false) | bool + - __proxmox__cluster.groups | default([]) | length > 0 + +- name: Include Proxmox datacenter user tasks + ansible.builtin.include_tasks: + file: datacenter/user.yml + loop: "{{ __proxmox__cluster.users }}" + loop_control: + loop_var: proxmox__dc_user + index_var: proxmox__dc_user_index + when: + - proxmox__role_enabled + - __proxmox__cluster.manage_users | default(false) | bool + - __proxmox__cluster.users | default([]) | length > 0 + +- name: Include Proxmox datacenter ACL tasks + ansible.builtin.include_tasks: + file: datacenter/acl.yml + loop: "{{ __proxmox__cluster.acls }}" + loop_control: + loop_var: proxmox__dc_acl + index_var: proxmox__dc_acl_index + when: + - proxmox__role_enabled + - __proxmox__cluster.manage_acls | default(false) | bool + - __proxmox__cluster.acls | default([]) | length > 0 \ No newline at end of file diff --git a/roles/proxmox/tasks/datacenter/acl.yml b/roles/proxmox/tasks/datacenter/acl.yml new file mode 100644 index 0000000..b790832 --- /dev/null +++ b/roles/proxmox/tasks/datacenter/acl.yml @@ -0,0 +1,9 @@ +--- +- name: Deploy Proxmox datacenter ACL + aybarsm.linux.proxmox_acl: + path: "{{ proxmox__dc_acl.path }}" + roles: "{{ proxmox__dc_acl.roles }}" + state: "{{ proxmox__dc_acl.state | default('present') }}" + groups: "{{ proxmox__dc_acl.groups | default([]) }}" + users: "{{ proxmox__dc_acl.users | default([]) }}" + register: proxmox__deploy_dc_acl \ No newline at end of file diff --git a/roles/proxmox/tasks/cluster_config.yml b/roles/proxmox/tasks/datacenter/config.yml similarity index 100% rename from roles/proxmox/tasks/cluster_config.yml rename to roles/proxmox/tasks/datacenter/config.yml diff --git a/roles/proxmox/tasks/datacenter/group.yml b/roles/proxmox/tasks/datacenter/group.yml new file mode 100644 index 0000000..01ab454 --- /dev/null +++ b/roles/proxmox/tasks/datacenter/group.yml @@ -0,0 +1,7 @@ +--- +- name: Deploy Proxmox datacenter user group + aybarsm.linux.proxmox_group: + name: "{{ proxmox__dc_group.name }}" + state: "{{ proxmox__dc_group.state | default('present') }}" + comment: "{{ proxmox__dc_group.comment | default(omit) }}" + register: proxmox__deploy_dc_group \ No newline at end of file diff --git a/roles/proxmox/tasks/datacenter/pool.yml b/roles/proxmox/tasks/datacenter/pool.yml new file mode 100644 index 0000000..67880ae --- /dev/null +++ b/roles/proxmox/tasks/datacenter/pool.yml @@ -0,0 +1,7 @@ +--- +- name: Deploy Proxmox datacenter pool + aybarsm.linux.proxmox_pool: + name: "{{ proxmox__dc_pool.name }}" + state: "{{ proxmox__dc_pool.state | default('present') }}" + comment: "{{ proxmox__dc_pool.comment | default(omit) }}" + register: proxmox__deploy_dc_pool \ No newline at end of file diff --git a/roles/proxmox/tasks/datacenter/role.yml b/roles/proxmox/tasks/datacenter/role.yml new file mode 100644 index 0000000..7c064fd --- /dev/null +++ b/roles/proxmox/tasks/datacenter/role.yml @@ -0,0 +1,7 @@ +--- +- name: Deploy Proxmox datacenter role + aybarsm.linux.proxmox_role: + name: "{{ proxmox__dc_role.name }}" + privileges: "{{ proxmox__dc_role.privileges }}" + state: "{{ proxmox__dc_role.state | default('present') }}" + register: proxmox__deploy_dc_role \ No newline at end of file diff --git a/roles/proxmox/tasks/datacenter/user.yml b/roles/proxmox/tasks/datacenter/user.yml new file mode 100644 index 0000000..2c235c0 --- /dev/null +++ b/roles/proxmox/tasks/datacenter/user.yml @@ -0,0 +1,14 @@ +--- +- name: Deploy Proxmox datacenter user + aybarsm.linux.proxmox_user: + name: "{{ proxmox__dc_user.name }}" + state: "{{ proxmox__dc_user.state | default('present') }}" + enable: "{{ proxmox__dc_user.enable | default(omit) }}" + groups: "{{ proxmox__dc_user.groups | default([]) }}" + comment: "{{ proxmox__dc_user.comment | default(omit) }}" + email: "{{ proxmox__dc_user.email | default(omit) }}" + firstname: "{{ proxmox__dc_user.firstname | default(omit) }}" + lastname: "{{ proxmox__dc_user.lastname | default(omit) }}" + password: "{{ proxmox__dc_user.password | default(omit) }}" + expire: "{{ proxmox__dc_user.expire | default(omit) }}" + register: proxmox__deploy_dc_user \ No newline at end of file diff --git a/roles/proxmox/tasks/main.yml b/roles/proxmox/tasks/main.yml index dae3251..d9292e3 100644 --- a/roles/proxmox/tasks/main.yml +++ b/roles/proxmox/tasks/main.yml @@ -1,11 +1,122 @@ --- -- name: Include Proxmox QEMU tasks +# - name: Import aybarsm.linux.network role for Hosts +# ansible.builtin.import_role: +# name: aybarsm.linux.network +# tasks_from: hosts.yml +# vars: +# network__use_only: ['host'] +# network__host: "{{ { +# 'ip': (__proxmox__cluster.members | map(attribute='links') | map('first')), +# 'hostname': (__proxmox__cluster.members | map(attribute='hostname')), +# 'fqdn': (__proxmox__cluster.members | map(attribute='fqdn'))} | +# aybarsm.helper.to_list_of_dicts({'entry__type': 'host'}) }}" +# when: +# - proxmox__role_enabled | default(false) | bool +# - __proxmox__cluster.members is defined + +# - name: Debug +# ansible.builtin.debug: +# msg: +# auth__host: "{{ auth__host }}" +# vars: +# auth__host: +# - entry__type: user +# name: root +# generate_ssh_key: true +# ssh_key_comment: "root@{{ proxmox__hostname }}" +# distribute_ssh_key: "{{ __proxmox__cluster.members | map(attribute='host') }}" +# delegate_to: localhost + +# - name: Include Proxmox authorized keys config tasks +# ansible.builtin.include_tasks: +# file: auth_key_config.yml +# when: +# - proxmox__role_enabled | default(false) | bool + +# - name: Import aybarsm.linux.auth role +# ansible.builtin.import_role: +# name: aybarsm.linux.auth +# vars: +# cluster_nodes: "{{ __proxmox__cluster.members | rejectattr('host', 'eq', inventory_hostname) }}" +# auth__role_enabled: true +# auth__manage_users: true +# auth__manage_authorized_keys: true +# auth__manage_sshd_config: true +# auth__manage_ssh_config: true +# auth__use_only: ['host'] +# auth__ssh_config_file: /root/.ssh/config +# auth__ssh_config_file_module: +# mode: '0600' +# owner: root +# group: root +# auth__ssh_changes_strategy: +# module: systemd_service +# immediate: false +# smart: true +# name: ssh.service +# enabled: true +# state: restarted +# auth__host: +# - entry__type: user +# entry__distribute_ssh_key: "{{ __proxmox__cluster.members | map(attribute='host') }}" +# name: root +# generate_ssh_key: true +# ssh_key_comment: "root@{{ proxmox__hostname }}" +# password: '$6$baqOo8I.hGLL0CIu$QlzIE8GhBpKBSY6sUUV9MROJyxtbwg2CPN86VZPv6jx23.vqwiL4/pAYQWaoeUia5NSTDVojtzlqsNTZBibGy/' +# shell: /bin/bash +# password_lock: false +# - entry__type: sshd_config +# name: PasswordAuthentication +# value: 'no' +# - entry__type: sshd_config +# name: PermitRootLogin +# value: 'no' +# - entry__type: sshd_config +# name: UseDNS +# value: 'no' +# - entry__type: sshd_config +# name: 'Match Address' +# value: "{{ cluster_nodes | map(attribute='links') | map('join', ',') | join(',') }}" +# children: +# - name: PermitRootLogin +# value: prohibit-password +# - entry__type: ssh_config +# name: Ciphers +# value: 'aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com' +# - entry__type: ssh_config +# name: Host +# value: "{{ cluster_nodes | aybarsm.helper.only_with(['hostname', 'fqdn', 'links']) | map('dict2items') | flatten | map(attribute='value') | flatten | join(' ') }}" +# children: +# - name: IdentityFile +# value: /root/.ssh/id_rsa +# - name: Port +# value: "{{ proxmox__ssh_port | default(22) }}" +# when: +# - proxmox__role_enabled | default(false) | bool +# - __proxmox__cluster.members is defined + +#FIXME: ONE TIME ONLY BEFORE CLUSTER!!! +# - name: Include Proxmox SSL Config tasks +# ansible.builtin.include_tasks: +# file: ssl_config.yml +# when: +# - proxmox__role_enabled | default(false) | bool +# - (proxmox__ssl_certificate | default(undef(), true)) is defined or (proxmox__ssl_key | default(undef(), true)) is defined + +- name: Include Proxmox datacenter tasks ansible.builtin.include_tasks: - file: qemu.yml - loop: "{{ proxmox__all | selectattr('entry__type', 'match', '^qemu_(vm|disk|nic)$') }}" - loop_control: - loop_var: proxmox__qemu_item - index_var: proxmox__qemu_item_index + file: datacenter/datacenter.yml when: - - proxmox__role_enabled | default(false) | bool - - proxmox__all | selectattr('entry__type', 'match', '^qemu_(vm|disk|nic)$') | default([]) | length > 0 \ No newline at end of file + - proxmox__role_enabled + - __proxmox__cluster.init | default('', true) == inventory_hostname + +# - name: Include Proxmox QEMU tasks +# ansible.builtin.include_tasks: +# file: qemu.yml +# loop: "{{ proxmox__all | selectattr('entry__type', 'match', '^qemu_(vm|disk|nic)$') }}" +# loop_control: +# loop_var: proxmox__qemu_item +# index_var: proxmox__qemu_item_index +# when: +# - proxmox__role_enabled | default(false) | bool +# - proxmox__all | selectattr('entry__type', 'match', '^qemu_(vm|disk|nic)$') | default([]) | length > 0 \ No newline at end of file diff --git a/roles/proxmox/tasks/qemu/disk.yml b/roles/proxmox/tasks/qemu/disk.yml index f826df1..388f1e0 100644 --- a/roles/proxmox/tasks/qemu/disk.yml +++ b/roles/proxmox/tasks/qemu/disk.yml @@ -1,8 +1,28 @@ --- +- name: Assert conditions as disk size calculation requested + ansible.builtin.assert: + that: + - qemu_disk.state | default('', true) == 'resized' + - qemu_disk.size is defined + - qemu_disk.disk is defined + - qemu_disk.size is match('^(\\d+)([A-Za-z]+)$') + fail_msg: "Disk size calculation requested but state is not resized or size is not defined in correct format." + success_msg: "Disk size calculation requested and state is resized and size is defined in correct format." + register: proxmox__assert_qemu_disk_calc_size + when: qemu_disk.entry__calc_size | default(false, true) + +- name: Include VM Info Task if calculation requested and conditions are met + ansible.builtin.include_tasks: + file: info/vm.yml + vars: + vm_info: "{{ qemu_disk | combine({'config': 'current'}) }}" + when: + - qemu_disk.entry__calc_size | default(false, true) + - proxmox__assert_qemu_disk_calc_size is success + - name: Proxmox QEMU DISK community.general.proxmox_disk: aio: "{{ qemu_disk.aio | default(omit, true) }}" - api_host: "{{ qemu_disk.api_host | default(omit, true) }}" api_host: "{{ qemu_disk.api_host }}" api_password: "{{ qemu_disk.api_password | default(omit, true) }}" api_port: "{{ qemu_disk.api_port | default(omit, true) }}" @@ -51,7 +71,7 @@ secs: "{{ qemu_disk.secs | default(omit, true) }}" serial: "{{ qemu_disk.serial | default(omit, true) }}" shared: "{{ qemu_disk.shared | default(omit, true) }}" - size: "{{ qemu_disk.size | default(omit, true) }}" + size: "{{ resolved_size | default(qemu_disk.size | default(omit, true), true) }}" snapshot: "{{ qemu_disk.snapshot | default(omit, true) }}" ssd: "{{ qemu_disk.ssd | default(omit, true) }}" state: "{{ qemu_disk.state | default(omit, true) }}" @@ -65,4 +85,16 @@ vmid: "{{ qemu_disk.vmid | default(omit, true) }}" werror: "{{ qemu_disk.werror | default(omit, true) }}" wwn: "{{ qemu_disk.wwn | default(omit, true) }}" + vars: + is_resolve_request: "{{ (qemu_disk.entry__calc_size | default(false, true)) and + proxmox__assert_qemu_disk_calc_size is success and + proxmox__info_vm.proxmox_vms[0][qemu_disk.disk] is defined }}" + current_size: "{{ (proxmox__info_vm.proxmox_vms[0][qemu_disk.disk] | regex_search('size=(\\w+)(,|$)', '\\1') | first) if is_resolve_request else undef() }}" + current_bytes: "{{ (current_size | human_to_bytes) if is_resolve_request else undef() }}" + wanted_bytes: "{{ (qemu_disk.size | human_to_bytes) if is_resolve_request else undef() }}" + diff_bytes: "{{ (wanted_bytes | int) - (current_bytes | int) if is_resolve_request else undef() }}" + is_eligible: "{{ (diff_bytes | int > 0) if is_resolve_request else false }}" + diff_size: "{{ (diff_bytes | int | human_readable) if is_eligible else undef() }}" + diff_size_format: "{{ (diff_size | regex_search('(\\d+)\\.(\\d+)\\s([A-Za-z])', '\\1', '\\2', '\\3')) if is_eligible else undef() }}" + resolved_size: "{{ ('+' + (diff_size_format | first) + (diff_size_format | last)) if is_eligible else undef() }}" register: proxmox__apply_qemu_disk \ No newline at end of file diff --git a/roles/proxmox/tasks/qemu/vm.yml b/roles/proxmox/tasks/qemu/vm.yml index 0e7e4a9..7f1143c 100644 --- a/roles/proxmox/tasks/qemu/vm.yml +++ b/roles/proxmox/tasks/qemu/vm.yml @@ -75,7 +75,7 @@ nameservers: "{{ qemu_vm.nameservers | default(omit, true) }}" net: "{{ qemu_vm.net | default(omit, true) }}" newid: "{{ qemu_vm.newid | default(omit, true) }}" - node: "{{ info_vm_node | default((qemu_vm.node | default(omit, true)), true) }}" + node: "{{ resolved_vm_node | default((qemu_vm.node | default(omit, true)), true) }}" numa: "{{ qemu_vm.numa | default(omit, true) }}" numa_enabled: "{{ qemu_vm.numa_enabled | default(omit, true) }}" onboot: "{{ qemu_vm.onboot | default(omit, true) }}" @@ -119,6 +119,6 @@ vmid: "{{ qemu_vm.vmid | default(omit, true) }}" watchdog: "{{ qemu_vm.watchdog | default(omit, true) }}" vars: - info_vm_node: "{{ proxmox__info_vm.proxmox_vms[0].node | default(undef(), true) if proxmox__assert_qemu_vm_node is failed else undef() }}" + resolved_vm_node: "{{ proxmox__info_vm.proxmox_vms[0].node | default((__proxmox__cluster.init | default(undef(), true)), true) if proxmox__assert_qemu_vm_node is failed else undef() }}" register: proxmox__apply_qemu_vm when: proxmox__assert_qemu_vm_identifier is success \ No newline at end of file diff --git a/roles/proxmox/tasks/ssl_config.yml b/roles/proxmox/tasks/ssl_config.yml new file mode 100644 index 0000000..792f2b2 --- /dev/null +++ b/roles/proxmox/tasks/ssl_config.yml @@ -0,0 +1,30 @@ +--- +- name: Import aybarsm.helper.file_mgr role for Proxmox SSL Config + ansible.builtin.import_role: + name: aybarsm.helper.file_mgr + vars: + file_mgr__role_enabled: true + file_mgr__use_only: ['host'] + file_mgr__host: + - entry__type: copy + entry__keep: "{{ (proxmox__ssl_certificate | default(undef(), true)) is defined }}" + content: "{{ lookup('file', proxmox__ssl_certificate) if (proxmox__ssl_certificate | default(undef(), true)) is defined else omit }}" + dest: /etc/pve/local/pve-ssl.pem + mode: '0640' + owner: root + group: 'www-data' + entry__handlers: + - entry__type: service + name: pveproxy + state: restarted + - entry__type: copy + entry__keep: "{{ (proxmox__ssl_key | default(undef(), true)) is defined }}" + content: "{{ lookup('file', proxmox__ssl_key) if (proxmox__ssl_key | default(undef(), true)) is defined else omit }}" + dest: /etc/pve/local/pve-ssl.key + mode: '0640' + owner: root + group: 'www-data' + entry__handlers: + - entry__type: service + name: pveproxy + state: restarted \ No newline at end of file diff --git a/roles/proxmox/vars/main.yml b/roles/proxmox/vars/main.yml index c692dad..a56ffd3 100644 --- a/roles/proxmox/vars/main.yml +++ b/roles/proxmox/vars/main.yml @@ -1,6 +1,17 @@ proxmox__all: "{{ {'host': proxmox__host, 'group': proxmox__group, 'default': proxmox__default} | aybarsm.helper.role_vars(only=proxmox__use_only) }}" -__proxmox__cluster: "{{ proxmox__clusters | selectattr('target', 'contains', inventory_hostname) | first | default({}, true) }}" +__proxmox__cluster_members_query: '*.{ + host: inventory_hostname, + hostname: proxmox__hostname, + domain: proxmox__domain, + fqdn: join(`.`, [not_null(proxmox__hostname, ``), not_null(proxmox__domain, ``)]), + links: proxmox__cluster_links} | + [?not_null(hostname) && not_null(domain) && not_null(links) && contains(__MEMBERS__, host)]' + +__proxmox__cluster: "{{ proxmox__clusters | selectattr('target', 'contains', inventory_hostname) | first | + combine({'members': (dict(hostvars) | community.general.json_query( __proxmox__cluster_members_query | + replace('__MEMBERS__', ('[`' + ((proxmox__clusters | selectattr('target', 'contains', inventory_hostname) | first)['target'] | join('`,`')) + '`]'))) | + sort(attribute='host'))}) if proxmox__clusters | selectattr('target', 'contains', inventory_hostname) | length > 0 else omit }}" __proxmox__module_vars: api_host: "{{ hostvars[__proxmox__cluster.init]['proxmox__cluster_links'] | first | default(omit, true) if __proxmox__cluster.init is defined else omit }}"