---
- name: Install OIDC certificate
  copy:
    content: "{{ kube_oidc_ca_cert | b64decode }}"
    dest: "{{ kube_oidc_ca_file }}"
    owner: root
    group: root
    mode: "0644"
  when:
    - kube_oidc_auth
    - kube_oidc_ca_cert is defined

- name: Kubeadm | Check if kubeadm has already run
  stat:
    path: "/var/lib/kubelet/config.yaml"
    get_attributes: false
    get_checksum: false
    get_mime: false
  register: kubeadm_already_run

- name: Kubeadm | Backup kubeadm certs / kubeconfig
  import_tasks: kubeadm-backup.yml
  when:
    - kubeadm_already_run.stat.exists

- name: Kubeadm | aggregate all SANs
  set_fact:
    apiserver_sans: "{{ _apiserver_sans | flatten | select | unique }}"
  vars:
    _apiserver_sans:
      - "kubernetes"
      - "kubernetes.default"
      - "kubernetes.default.svc"
      - "kubernetes.default.svc.{{ dns_domain }}"
      - "{{ kube_apiserver_ip }}"
      - "localhost"
      - "127.0.0.1"
      - "::1"
      - "{{ apiserver_loadbalancer_domain_name | d('') }}"
      - "{{ loadbalancer_apiserver.address | d('') }}"
      - "{{ supplementary_addresses_in_ssl_keys }}"
      - "{{ groups['kube_control_plane'] | map('extract', hostvars, 'main_access_ip') }}"
      - "{{ groups['kube_control_plane'] | map('extract', hostvars, 'main_ip') }}"
      - "{{ groups['kube_control_plane'] | map('extract', hostvars, ['ansible_default_ipv4', 'address']) | select('defined') }}"
      - "{{ groups['kube_control_plane'] | map('extract', hostvars, ['ansible_default_ipv6', 'address']) | select('defined') }}"
      - "{{ groups['kube_control_plane'] | map('extract', hostvars, 'ansible_hostname') }}"
      - "{{ groups['kube_control_plane'] | map('extract', hostvars, 'ansible_fqdn') }}"
      - "{{ kube_override_hostname }}"
      - "{{ kube_vip_address }}"
  tags: facts

- name: Create audit-policy directory
  file:
    path: "{{ audit_policy_file | dirname }}"
    state: directory
    mode: "0640"
  when: kubernetes_audit or kubernetes_audit_webhook

- name: Write api audit policy yaml
  template:
    src: apiserver-audit-policy.yaml.j2
    dest: "{{ audit_policy_file }}"
    mode: "0640"
  when: kubernetes_audit or kubernetes_audit_webhook
  notify: Control plane | Restart apiserver

- name: Write api audit webhook config yaml
  template:
    src: apiserver-audit-webhook-config.yaml.j2
    dest: "{{ audit_webhook_config_file }}"
    mode: "0640"
  when: kubernetes_audit_webhook
  notify: Control plane | Restart apiserver

- name: Create apiserver tracing config directory
  file:
    path: "{{ kube_config_dir }}/tracing"
    state: directory
    mode: "0640"
  when: kube_apiserver_tracing

- name: Write apiserver tracing config yaml
  template:
    src: apiserver-tracing.yaml.j2
    dest: "{{ kube_config_dir }}/tracing/apiserver-tracing.yaml"
    mode: "0640"
  when: kube_apiserver_tracing
  notify: Control plane | Restart apiserver

# Nginx LB(default), If kubeadm_config_api_fqdn is defined, use other LB by kubeadm controlPlaneEndpoint.
- name: Set kubeadm_config_api_fqdn define
  set_fact:
    kubeadm_config_api_fqdn: "{{ apiserver_loadbalancer_domain_name }}"
  when: loadbalancer_apiserver is defined

- name: Kubeadm | Create kubeadm config
  template:
    src: "kubeadm-config.v1beta4.yaml.j2"
    dest: "{{ kube_config_dir }}/kubeadm-config.yaml"
    mode: "0640"
    validate: "{{ kubeadm_config_validate_enabled | ternary(bin_dir + '/kubeadm config validate --config %s', omit) }}"

- name: Kubeadm | Create directory to store admission control configurations
  file:
    path: "{{ kube_config_dir }}/admission-controls"
    state: directory
    mode: "0640"
  when: kube_apiserver_admission_control_config_file

- name: Kubeadm | Push admission control config file
  template:
    src: "admission-controls.yaml.j2"
    dest: "{{ kube_config_dir }}/admission-controls/admission-controls.yaml"
    mode: "0640"
  when: kube_apiserver_admission_control_config_file
  notify: Control plane | Restart apiserver

- name: Kubeadm | Push admission control config files
  template:
    src: "{{ item | lower }}.yaml.j2"
    dest: "{{ kube_config_dir }}/admission-controls/{{ item | lower }}.yaml"
    mode: "0640"
  when:
    - kube_apiserver_admission_control_config_file
    - item in kube_apiserver_admission_plugins_needs_configuration
  loop: "{{ kube_apiserver_enable_admission_plugins }}"
  notify: Control plane | Restart apiserver

- name: Kubeadm | Check apiserver.crt SANs
  vars:
    apiserver_ips: "{{ apiserver_sans | map('ansible.utils.ipaddr') | reject('equalto', False) | list }}"
    apiserver_hosts: "{{ apiserver_sans | difference(apiserver_ips) }}"
  when:
    - kubeadm_already_run.stat.exists
    - not kube_external_ca_mode
  block:
    - name: Kubeadm | Check apiserver.crt SAN IPs
      command:
        cmd: "openssl x509 -noout -in {{ kube_cert_dir }}/apiserver.crt -checkip {{ item }}"
      loop: "{{ apiserver_ips }}"
      register: apiserver_sans_ip_check
      changed_when: apiserver_sans_ip_check.stdout is not search('does match certificate')
      failed_when: apiserver_sans_ip_check.rc != 0 and apiserver_sans_ip_check.stdout is not search('does NOT match certificate')
    - name: Kubeadm | Check apiserver.crt SAN hosts
      command:
        cmd: "openssl x509 -noout -in {{ kube_cert_dir }}/apiserver.crt -checkhost {{ item }}"
      loop: "{{ apiserver_hosts }}"
      register: apiserver_sans_host_check
      changed_when: apiserver_sans_host_check.stdout is not search('does match certificate')
      failed_when: apiserver_sans_host_check.rc != 0 and apiserver_sans_host_check.stdout is not search('does NOT match certificate')

- name: Kubeadm | regenerate apiserver cert 1/2
  file:
    state: absent
    path: "{{ kube_cert_dir }}/{{ item }}"
  with_items:
    - apiserver.crt
    - apiserver.key
  when:
    - kubeadm_already_run.stat.exists
    - apiserver_sans_ip_check.changed or apiserver_sans_host_check.changed
    - not kube_external_ca_mode

- name: Kubeadm | regenerate apiserver cert 2/2
  command: >-
    {{ bin_dir }}/kubeadm
    init phase certs apiserver
    --config={{ kube_config_dir }}/kubeadm-config.yaml
  when:
    - kubeadm_already_run.stat.exists
    - apiserver_sans_ip_check.changed or apiserver_sans_host_check.changed
    - not kube_external_ca_mode

  # TODO: Remove --skip-phases from command when v1beta4 UpgradeConfiguration supports skipPhases
- name: Kubeadm | Initialize first control plane node
  when: inventory_hostname == first_kube_control_plane and not kubeadm_already_run.stat.exists
  vars:
    kubeadm_init_first_control_plane_cmd: >-
      timeout -k {{ kubeadm_init_timeout }} {{ kubeadm_init_timeout }}
      {{ bin_dir }}/kubeadm init
      --config={{ kube_config_dir }}/kubeadm-config.yaml
      --ignore-preflight-errors={{ _ignore_errors | flatten | join(',') }}
      --skip-phases={{ kubeadm_init_phases_skip | join(',') }}
      {{ kube_external_ca_mode | ternary('', '--upload-certs') }}
    _ignore_errors: "{{ kubeadm_ignore_preflight_errors }}"
  environment:
    PATH: "{{ bin_dir }}:{{ ansible_env.PATH }}"
  notify: Control plane | restart kubelet
  block:
    - name: Kubeadm | Initialize first control plane node (1st try)
      command: "{{ kubeadm_init_first_control_plane_cmd }}"
      register: kubeadm_init
      failed_when: kubeadm_init.rc != 0 and "field is immutable" not in kubeadm_init.stderr
  rescue:
    # Retry is because upload config sometimes fails
    # This retry task is separated from 1st task to show log of failure of 1st task.
    - name: Kubeadm | Initialize first control plane node (retry)
      command: "{{ kubeadm_init_first_control_plane_cmd }}"
      vars:
        _errors_from_first_try:
          - 'FileAvailable--etc-kubernetes-manifests-kube-controller-manager.yaml'
          - 'FileAvailable--etc-kubernetes-manifests-kube-scheduler.yaml'
          - 'FileAvailable--etc-kubernetes-manifests-kube-apiserver.yaml'
          - 'Port-10250'
        _ignore_errors:
          - "{{ kubeadm_ignore_preflight_errors }}"
          - "{{ _errors_from_first_try if 'all' not in kubeadm_ignore_preflight_errors else [] }}"
      register: kubeadm_init
      retries: 2
      until: kubeadm_init is succeeded or "field is immutable" in kubeadm_init.stderr
      failed_when: kubeadm_init.rc != 0 and "field is immutable" not in kubeadm_init.stderr

- name: Set kubeadm certificate key
  set_fact:
    kubeadm_certificate_key: "{{ item | regex_search('--certificate-key ([^ ]+)', '\\1') | first }}"
  with_items: "{{ hostvars[groups['kube_control_plane'][0]]['kubeadm_init'].stdout_lines | default([]) }}"
  when:
    - kubeadm_certificate_key is not defined
    - (item | trim) is match('.*--certificate-key.*')

- name: Create hardcoded kubeadm token for joining nodes with 24h expiration (if defined)
  shell: >-
    {{ bin_dir }}/kubeadm --kubeconfig {{ kube_config_dir }}/admin.conf token delete {{ kubeadm_token }} || :;
    {{ bin_dir }}/kubeadm --kubeconfig {{ kube_config_dir }}/admin.conf token create {{ kubeadm_token }}
  changed_when: false
  when:
    - inventory_hostname == first_kube_control_plane
    - kubeadm_token is defined
    - kubeadm_refresh_token
  tags:
    - kubeadm_token

- name: Remove binding to anonymous user
  command: "{{ kubectl }} -n kube-public delete rolebinding kubeadm:bootstrap-signer-clusterinfo --ignore-not-found"
  when: inventory_hostname == first_kube_control_plane and remove_anonymous_access

- name: Create kubeadm token for joining nodes with 24h expiration (default)
  command: "{{ bin_dir }}/kubeadm --kubeconfig {{ kube_config_dir }}/admin.conf token create"
  changed_when: false
  register: temp_token
  retries: 5
  delay: 5
  until: temp_token is succeeded
  delegate_to: "{{ first_kube_control_plane }}"
  when: kubeadm_token is not defined
  tags:
    - kubeadm_token

- name: Set kubeadm_token
  set_fact:
    kubeadm_token: "{{ temp_token.stdout }}"
  when: temp_token.stdout is defined
  tags:
    - kubeadm_token

- name: Kubeadm | Join other control plane nodes
  include_tasks: kubeadm-secondary.yml

- name: Kubeadm | upgrade kubernetes cluster to {{ kube_version }}
  include_tasks: kubeadm-upgrade.yml
  when:
    - upgrade_cluster_setup
    - kubeadm_already_run.stat.exists

# FIXME(mattymo): from docs: If you don't want to taint your control-plane node, set this field to an empty slice, i.e. `taints: {}` in the YAML file.
- name: Kubeadm | Remove taint for control plane node with node role
  command: "{{ kubectl }} taint node {{ inventory_hostname }} {{ item }}"
  delegate_to: "{{ first_kube_control_plane }}"
  with_items:
    - "node-role.kubernetes.io/control-plane:NoSchedule-"
  when: ('kube_node' in group_names)
  failed_when: false
