diff --git a/ansible/playbooks/coachlight-infra-stack.yml b/ansible/playbooks/coachlight-infra-stack.yml index 31eb29c..e699d40 100644 --- a/ansible/playbooks/coachlight-infra-stack.yml +++ b/ansible/playbooks/coachlight-infra-stack.yml @@ -54,7 +54,14 @@ vars: k8s_validator_kubeconfig: "{{ kubeconfig_manager_artifacts.target_kubeconfig_path }}" k8s_validator_context: "{{ kubeconfig_manager_artifacts.context_name }}" + - role: proxmox_homepage_token + vars: + proxmox_homepage_token_op_service_account_token: "{{ lookup('env', 'OP_SERVICE_ACCOUNT_TOKEN') }}" - role: homepage_deploy + vars: + homepage_deploy_proxmox_url: "{{ proxmox_homepage_token_artifacts.url | default('') }}" + homepage_deploy_proxmox_username: "{{ proxmox_homepage_token_artifacts.username | default('') }}" + homepage_deploy_proxmox_secret: "{{ proxmox_homepage_token_artifacts.secret | default('') }}" # - role: argocd_api_auth # vars: # argocd_api_auth_username: "{{ argocd_admin_username }}" diff --git a/ansible/roles/homepage_deploy/defaults/main.yml b/ansible/roles/homepage_deploy/defaults/main.yml index ecfe186..e66b712 100644 --- a/ansible/roles/homepage_deploy/defaults/main.yml +++ b/ansible/roles/homepage_deploy/defaults/main.yml @@ -11,3 +11,8 @@ homepage_deploy_application_name: homepage homepage_deploy_ingress_templates_root: "{{ role_path }}/templates/ingresses" homepage_deploy_artifacts_path: "{{ artifacts_path }}" + +homepage_deploy_proxmox_node_name: "{{ groups['proxmox'][0] | default('pve') }}" +homepage_deploy_proxmox_url: "" +homepage_deploy_proxmox_username: "" +homepage_deploy_proxmox_secret: "" diff --git a/ansible/roles/homepage_deploy/meta/argument_specs.yml b/ansible/roles/homepage_deploy/meta/argument_specs.yml index 5e653ac..4ecf1d1 100644 --- a/ansible/roles/homepage_deploy/meta/argument_specs.yml +++ b/ansible/roles/homepage_deploy/meta/argument_specs.yml @@ -28,3 +28,19 @@ argument_specs: homepage_deploy_artifacts_path: type: str required: false + homepage_deploy_proxmox_node_name: + type: str + required: false + description: Proxmox node name for the widget configuration. + homepage_deploy_proxmox_url: + type: str + required: false + description: Proxmox API URL. + homepage_deploy_proxmox_username: + type: str + required: false + description: Proxmox API username (user@realm!tokenid). + homepage_deploy_proxmox_secret: + type: str + required: false + description: Proxmox API token secret. diff --git a/ansible/roles/homepage_deploy/tasks/main.yml b/ansible/roles/homepage_deploy/tasks/main.yml index cedc8b3..ce470f4 100644 --- a/ansible/roles/homepage_deploy/tasks/main.yml +++ b/ansible/roles/homepage_deploy/tasks/main.yml @@ -3,6 +3,15 @@ state: present definition: "{{ lookup('ansible.builtin.template', 'homepage-application.yml.j2') }}" +- name: Apply Homepage Proxmox ConfigMap when credentials are provided + kubernetes.core.k8s: + state: present + definition: "{{ lookup('ansible.builtin.template', 'homepage-proxmox-configmap.yml.j2') }}" + when: + - homepage_deploy_proxmox_url | length > 0 + - homepage_deploy_proxmox_username | length > 0 + - homepage_deploy_proxmox_secret | length > 0 + - name: Discover Homepage ingress templates ansible.builtin.find: paths: "{{ homepage_deploy_ingress_templates_root }}" diff --git a/ansible/roles/homepage_deploy/templates/homepage-application.yml.j2 b/ansible/roles/homepage_deploy/templates/homepage-application.yml.j2 index 7ccdb0a..6dd124d 100644 --- a/ansible/roles/homepage_deploy/templates/homepage-application.yml.j2 +++ b/ansible/roles/homepage_deploy/templates/homepage-application.yml.j2 @@ -76,6 +76,17 @@ spec: main: enabled: false +{% if homepage_deploy_proxmox_url | length > 0 and homepage_deploy_proxmox_username | length > 0 and homepage_deploy_proxmox_secret | length > 0 %} + persistence: + proxmox: + enabled: true + type: configMap + name: homepage-proxmox-config + mountPath: /app/config/proxmox.yaml + subPath: proxmox.yaml + readOnly: true +{% endif %} + env: - name: HOMEPAGE_ALLOWED_HOSTS value: "homepage.rohu-shark.ts.net" diff --git a/ansible/roles/homepage_deploy/templates/homepage-proxmox-configmap.yml.j2 b/ansible/roles/homepage_deploy/templates/homepage-proxmox-configmap.yml.j2 new file mode 100644 index 0000000..1acd83e --- /dev/null +++ b/ansible/roles/homepage_deploy/templates/homepage-proxmox-configmap.yml.j2 @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: homepage-proxmox-config + namespace: {{ homepage_deploy_namespace }} +data: + proxmox.yaml: | + {{ homepage_deploy_proxmox_node_name }}: + url: {{ homepage_deploy_proxmox_url }} + username: {{ homepage_deploy_proxmox_username }} + password: {{ homepage_deploy_proxmox_secret }} diff --git a/ansible/roles/proxmox_homepage_token/defaults/main.yml b/ansible/roles/proxmox_homepage_token/defaults/main.yml new file mode 100644 index 0000000..1029fd6 --- /dev/null +++ b/ansible/roles/proxmox_homepage_token/defaults/main.yml @@ -0,0 +1,9 @@ +proxmox_homepage_token_user: api@pam +proxmox_homepage_token_group: api-ro-users +proxmox_homepage_token_role: PVEAuditor +proxmox_homepage_token_id: homepage +proxmox_homepage_token_1p_vault: coachlight-homelab +proxmox_homepage_token_1p_item_title: proxmox-homepage-token +proxmox_homepage_token_url: "https://{{ hostvars[groups['proxmox'][0]].ansible_host }}:8006" +proxmox_homepage_token_primary_node: "{{ groups['proxmox'][0] }}" +proxmox_homepage_token_artifacts_path: "{{ artifacts_path }}" diff --git a/ansible/roles/proxmox_homepage_token/meta/argument_specs.yml b/ansible/roles/proxmox_homepage_token/meta/argument_specs.yml new file mode 100644 index 0000000..5037801 --- /dev/null +++ b/ansible/roles/proxmox_homepage_token/meta/argument_specs.yml @@ -0,0 +1,43 @@ +argument_specs: + main: + options: + proxmox_homepage_token_user: + type: str + required: false + description: Proxmox user for Homepage API token (e.g., api@pam). + proxmox_homepage_token_group: + type: str + required: false + description: Proxmox group for read-only API access. + proxmox_homepage_token_role: + type: str + required: false + description: Proxmox role to assign to the group (e.g., PVEAuditor). + proxmox_homepage_token_id: + type: str + required: false + description: Token ID for the Homepage token. + proxmox_homepage_token_1p_vault: + type: str + required: false + description: 1Password vault name for storing the token. + proxmox_homepage_token_1p_item_title: + type: str + required: false + description: 1Password item title for the token. + proxmox_homepage_token_url: + type: str + required: false + description: Proxmox API URL including scheme and port. + proxmox_homepage_token_primary_node: + type: str + required: false + description: Primary Proxmox node to run pveum commands on. + proxmox_homepage_token_artifacts_path: + type: str + required: false + description: Base path for artifacts output. + proxmox_homepage_token_op_service_account_token: + type: str + required: false + description: 1Password service account token for API access. diff --git a/ansible/roles/proxmox_homepage_token/tasks/main.yml b/ansible/roles/proxmox_homepage_token/tasks/main.yml new file mode 100644 index 0000000..cb99332 --- /dev/null +++ b/ansible/roles/proxmox_homepage_token/tasks/main.yml @@ -0,0 +1,174 @@ +- name: Ensure 1Password vault exists for {{ proxmox_homepage_token_1p_vault }} + ansible.builtin.include_role: + name: op_vault_validator + vars: + op_vault_validator_vault_name: "{{ proxmox_homepage_token_1p_vault }}" + op_vault_validator_op_service_account_token: "{{ proxmox_homepage_token_op_service_account_token }}" + op_vault_validator_artifacts_path: "{{ proxmox_homepage_token_artifacts_path }}/op_vault_validator" + +- name: Initialize proxmox_homepage_token facts + ansible.builtin.set_fact: + proxmox_homepage_token_url_value: "" + proxmox_homepage_token_username_value: "" + proxmox_homepage_token_secret_value: "" + proxmox_homepage_token_exists: false + +- name: Check if 1Password item {{ proxmox_homepage_token_1p_item_title }} exists + ansible.builtin.command: + cmd: >- + op item get {{ proxmox_homepage_token_1p_item_title }} + --vault {{ proxmox_homepage_token_1p_vault }} + --format json + environment: + OP_SERVICE_ACCOUNT_TOKEN: "{{ proxmox_homepage_token_op_service_account_token }}" + register: proxmox_homepage_token_1p_item_check + changed_when: false + failed_when: false + +- name: Read existing token from 1Password when item exists + ansible.builtin.include_role: + name: op_read + vars: + op_read_items: + - vault_name: "{{ proxmox_homepage_token_1p_vault }}" + item_name: "{{ proxmox_homepage_token_1p_item_title }}" + field_name: url + - vault_name: "{{ proxmox_homepage_token_1p_vault }}" + item_name: "{{ proxmox_homepage_token_1p_item_title }}" + field_name: username + - vault_name: "{{ proxmox_homepage_token_1p_vault }}" + item_name: "{{ proxmox_homepage_token_1p_item_title }}" + field_name: credential + op_read_op_service_account_token: "{{ proxmox_homepage_token_op_service_account_token }}" + op_read_artifacts_path: "{{ proxmox_homepage_token_artifacts_path }}/op_read" + when: proxmox_homepage_token_1p_item_check.rc == 0 + +- name: Set facts from existing 1Password item when present + ansible.builtin.set_fact: + proxmox_homepage_token_url_value: "{{ op_read_artifacts.items | selectattr('field_name', 'equalto', 'url') | map(attribute='value') | first }}" + proxmox_homepage_token_username_value: "{{ op_read_artifacts.items | selectattr('field_name', 'equalto', 'username') | map(attribute='value') | first }}" + proxmox_homepage_token_secret_value: "{{ op_read_artifacts.items | selectattr('field_name', 'equalto', 'credential') | map(attribute='value') | first }}" + proxmox_homepage_token_exists: true + when: proxmox_homepage_token_1p_item_check.rc == 0 + +- name: Ensure Proxmox group {{ proxmox_homepage_token_group }} exists + ansible.builtin.command: + cmd: pveum group add {{ proxmox_homepage_token_group }} + delegate_to: "{{ proxmox_homepage_token_primary_node }}" + register: proxmox_homepage_token_group_add + changed_when: "'already exists' not in proxmox_homepage_token_group_add.stderr" + failed_when: + - proxmox_homepage_token_group_add.rc != 0 + - "'already exists' not in proxmox_homepage_token_group_add.stderr" + when: proxmox_homepage_token_1p_item_check.rc != 0 + +- name: Ensure Proxmox user {{ proxmox_homepage_token_user }} exists + ansible.builtin.command: + cmd: pveum user add {{ proxmox_homepage_token_user }} + delegate_to: "{{ proxmox_homepage_token_primary_node }}" + register: proxmox_homepage_token_user_add + changed_when: "'already exists' not in proxmox_homepage_token_user_add.stderr" + failed_when: + - proxmox_homepage_token_user_add.rc != 0 + - "'already exists' not in proxmox_homepage_token_user_add.stderr" + when: proxmox_homepage_token_1p_item_check.rc != 0 + +- name: Add user {{ proxmox_homepage_token_user }} to group {{ proxmox_homepage_token_group }} + ansible.builtin.command: + cmd: pveum user modify {{ proxmox_homepage_token_user }} -group {{ proxmox_homepage_token_group }} + delegate_to: "{{ proxmox_homepage_token_primary_node }}" + register: proxmox_homepage_token_user_modify + changed_when: true + when: proxmox_homepage_token_1p_item_check.rc != 0 + +- name: Set ACL for group {{ proxmox_homepage_token_group }} with role {{ proxmox_homepage_token_role }} + ansible.builtin.command: + cmd: >- + pveum acl modify / + -group {{ proxmox_homepage_token_group }} + -role {{ proxmox_homepage_token_role }} + -propagate 1 + delegate_to: "{{ proxmox_homepage_token_primary_node }}" + register: proxmox_homepage_token_acl_modify + changed_when: true + when: proxmox_homepage_token_1p_item_check.rc != 0 + +- name: Check if API token {{ proxmox_homepage_token_user }}!{{ proxmox_homepage_token_id }} exists + ansible.builtin.command: + cmd: pveum user token list {{ proxmox_homepage_token_user }} + delegate_to: "{{ proxmox_homepage_token_primary_node }}" + register: proxmox_homepage_token_list + changed_when: false + when: proxmox_homepage_token_1p_item_check.rc != 0 + +- name: Remove existing API token {{ proxmox_homepage_token_user }}!{{ proxmox_homepage_token_id }} if present + ansible.builtin.command: + cmd: pveum user token remove {{ proxmox_homepage_token_user }} {{ proxmox_homepage_token_id }} + delegate_to: "{{ proxmox_homepage_token_primary_node }}" + register: proxmox_homepage_token_remove + changed_when: true + when: + - proxmox_homepage_token_1p_item_check.rc != 0 + - proxmox_homepage_token_list.stdout is defined + - proxmox_homepage_token_id in proxmox_homepage_token_list.stdout + +- name: Create API token {{ proxmox_homepage_token_user }}!{{ proxmox_homepage_token_id }} with privsep + ansible.builtin.command: + cmd: pveum user token add {{ proxmox_homepage_token_user }} {{ proxmox_homepage_token_id }} --privsep 1 --output-format json + delegate_to: "{{ proxmox_homepage_token_primary_node }}" + register: proxmox_homepage_token_create + changed_when: true + when: proxmox_homepage_token_1p_item_check.rc != 0 + +- name: Parse token creation output when token was created + ansible.builtin.set_fact: + proxmox_homepage_token_secret_new: "{{ (proxmox_homepage_token_create.stdout | from_json).value }}" + when: + - proxmox_homepage_token_1p_item_check.rc != 0 + - proxmox_homepage_token_create is defined + - proxmox_homepage_token_create.stdout is defined + +- name: Create 1Password item for new token + ansible.builtin.include_role: + name: op_item_create + vars: + op_item_create_vault_name: "{{ proxmox_homepage_token_1p_vault }}" + op_item_create_op_service_account_token: "{{ proxmox_homepage_token_op_service_account_token }}" + op_item_create_artifacts_path: "{{ proxmox_homepage_token_artifacts_path }}/op_item_create" + op_item_create_items: + - name: "{{ proxmox_homepage_token_1p_item_title }}" + category: api_credential + url: "{{ proxmox_homepage_token_url }}" + tags: + - ansible_managed + - proxmox + - homepage + fields: + username[text]: "{{ proxmox_homepage_token_user }}!{{ proxmox_homepage_token_id }}" + credential[concealed]: "{{ proxmox_homepage_token_secret_new }}" + when: proxmox_homepage_token_1p_item_check.rc != 0 + +- name: Set facts from newly created token + ansible.builtin.set_fact: + proxmox_homepage_token_url_value: "{{ proxmox_homepage_token_url }}" + proxmox_homepage_token_username_value: "{{ proxmox_homepage_token_user }}!{{ proxmox_homepage_token_id }}" + proxmox_homepage_token_secret_value: "{{ proxmox_homepage_token_secret_new }}" + proxmox_homepage_token_exists: true + when: proxmox_homepage_token_1p_item_check.rc != 0 + +- name: Build proxmox_homepage_token_artifacts + ansible.builtin.set_fact: + proxmox_homepage_token_artifacts: + url: "{{ proxmox_homepage_token_url_value }}" + username: "{{ proxmox_homepage_token_username_value }}" + secret: "{{ proxmox_homepage_token_secret_value }}" + exists: "{{ proxmox_homepage_token_exists }}" + +- name: Record proxmox_homepage_token artifacts + ansible.builtin.include_role: + name: role_artifacts + vars: + # noqa: var-naming + role_artifacts_path: "{{ proxmox_homepage_token_artifacts_path }}" + calling_role_name: proxmox_homepage_token + calling_role_artifacts_inputs: "{{ proxmox_homepage_token_artifacts }}"