---
- name: Calico | Install Wireguard packages
  package:
    name: "{{ item }}"
    state: present
  with_items: "{{ calico_wireguard_packages }}"
  register: calico_package_install
  until: calico_package_install is succeeded
  retries: 4
  when: calico_wireguard_enabled

- name: Calico | Copy calicoctl binary from download dir
  copy:
    src: "{{ downloads.calicoctl.dest }}"
    dest: "{{ bin_dir }}/calicoctl"
    mode: "0755"
    remote_src: true

- name: Calico | Create calico certs directory
  file:
    dest: "{{ calico_cert_dir }}"
    state: directory
    mode: "0750"
    owner: root
    group: root
  when: calico_datastore == "etcd"

- name: Calico | Link etcd certificates for calico-node
  file:
    src: "{{ etcd_cert_dir }}/{{ item.s }}"
    dest: "{{ calico_cert_dir }}/{{ item.d }}"
    state: hard
    mode: "0640"
    force: true
  with_items:
    - {s: "{{ kube_etcd_cacert_file }}", d: "ca_cert.crt"}
    - {s: "{{ kube_etcd_cert_file }}", d: "cert.crt"}
    - {s: "{{ kube_etcd_key_file }}", d: "key.pem"}
  when: calico_datastore == "etcd"

- name: Calico | Generate typha certs
  include_tasks: typha_certs.yml
  when:
    - typha_secure
    - inventory_hostname == groups['kube_control_plane'][0]

- name: Calico | Generate apiserver certs
  include_tasks: calico_apiserver_certs.yml
  when:
    - calico_apiserver_enabled
    - inventory_hostname == groups['kube_control_plane'][0]

- name: Calico | Install calicoctl wrapper script
  template:
    src: "calicoctl.{{ calico_datastore }}.sh.j2"
    dest: "{{ bin_dir }}/calicoctl.sh"
    mode: "0755"
    owner: root
    group: root

- name: Calico | wait for etcd
  uri:
    url: "{{ etcd_access_addresses.split(',') | first }}/health"
    validate_certs: false
    client_cert: "{{ calico_cert_dir }}/cert.crt"
    client_key: "{{ calico_cert_dir }}/key.pem"
  register: result
  until: result.status == 200 or result.status == 401
  retries: 10
  delay: 5
  run_once: true
  when: calico_datastore == "etcd"

- name: Calico | Check if calico network pool has already been configured
  # noqa risky-shell-pipe - grep will exit 1 if no match found
  shell: >
    {{ bin_dir }}/calicoctl.sh get ippool | grep -w "{{ calico_pool_cidr | default(kube_pods_subnet) }}" | wc -l
  args:
    executable: /bin/bash
  register: calico_conf
  retries: 4
  until: calico_conf.rc == 0
  delay: "{{ retry_stagger | random + 3 }}"
  changed_when: false
  when:
    - inventory_hostname == groups['kube_control_plane'][0]

- name: Calico | Ensure that calico_pool_cidr is within kube_pods_subnet when defined
  assert:
    that: "[calico_pool_cidr] | ansible.utils.ipaddr(kube_pods_subnet) | length == 1"
    msg: "{{ calico_pool_cidr }} is not within or equal to {{ kube_pods_subnet }}"
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
    - 'calico_conf.stdout == "0"'
    - calico_pool_cidr is defined

- name: Calico | Check if calico IPv6 network pool has already been configured
  # noqa risky-shell-pipe - grep will exit 1 if no match found
  shell: >
    {{ bin_dir }}/calicoctl.sh get ippool | grep -w "{{ calico_pool_cidr_ipv6 | default(kube_pods_subnet_ipv6) }}" | wc -l
  args:
    executable: /bin/bash
  register: calico_conf_ipv6
  retries: 4
  until: calico_conf_ipv6.rc == 0
  delay: "{{ retry_stagger | random + 3 }}"
  changed_when: false
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
    - enable_dual_stack_networks

- name: Calico | Ensure that calico_pool_cidr_ipv6 is within kube_pods_subnet_ipv6 when defined
  assert:
    that: "[calico_pool_cidr_ipv6] | ansible.utils.ipaddr(kube_pods_subnet_ipv6) | length == 1"
    msg: "{{ calico_pool_cidr_ipv6 }} is not within or equal to {{ kube_pods_subnet_ipv6 }}"
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
    - calico_conf_ipv6.stdout is defined and calico_conf_ipv6.stdout == "0"
    - calico_pool_cidr_ipv6 is defined
    - enable_dual_stack_networks

- name: Calico | kdd specific configuration
  when:
    - inventory_hostname in groups['kube_control_plane']
    - calico_datastore == "kdd"
  block:
    - name: Calico | Check if extra directory is needed
      stat:
        path: "{{ local_release_dir }}/calico-{{ calico_version }}-kdd-crds/{{ 'kdd' if (calico_version is version('v3.22.3', '<')) else 'crd' }}"
      register: kdd_path
    - name: Calico | Set kdd path when calico < v3.22.3
      set_fact:
        calico_kdd_path: "{{ local_release_dir }}/calico-{{ calico_version }}-kdd-crds{{ '/kdd' if kdd_path.stat.exists is defined and kdd_path.stat.exists }}"
      when:
        - calico_version is version('v3.22.3', '<')
    - name: Calico | Set kdd path when calico > v3.22.2
      set_fact:
        calico_kdd_path: "{{ local_release_dir }}/calico-{{ calico_version }}-kdd-crds{{ '/crd' if kdd_path.stat.exists is defined and kdd_path.stat.exists }}"
      when:
        - calico_version is version('v3.22.2', '>')
    - name: Calico | Create calico manifests for kdd
      assemble:
        src: "{{ calico_kdd_path }}"
        dest: "{{ kube_config_dir }}/kdd-crds.yml"
        mode: "0644"
        delimiter: "---\n"
        regexp: ".*\\.yaml"
        remote_src: true

    - name: Calico | Create Calico Kubernetes datastore resources
      kube:
        kubectl: "{{ bin_dir }}/kubectl"
        filename: "{{ kube_config_dir }}/kdd-crds.yml"
        state: "latest"
      register: kubectl_result
      until: kubectl_result is succeeded
      retries: 5
      when:
        - inventory_hostname == groups['kube_control_plane'][0]

- name: Calico | Configure Felix
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
  block:
    - name: Calico | Get existing FelixConfiguration
      command: "{{ bin_dir }}/calicoctl.sh get felixconfig default -o json"
      register: _felix_cmd
      ignore_errors: true
      changed_when: false

    - name: Calico | Set kubespray FelixConfiguration
      set_fact:
        _felix_config: >
          {
            "kind": "FelixConfiguration",
            "apiVersion": "projectcalico.org/v3",
            "metadata": {
              "name": "default",
            },
            "spec": {
              "ipipEnabled": {{ calico_ipip_mode != 'Never' }},
              "reportingInterval": "{{ calico_felix_reporting_interval }}",
              "bpfLogLevel": "{{ calico_bpf_log_level }}",
              "bpfEnabled": {{ calico_bpf_enabled | bool }},
              "bpfExternalServiceMode": "{{ calico_bpf_service_mode }}",
              "wireguardEnabled": {{ calico_wireguard_enabled | bool }},
              "logSeverityScreen": "{{ calico_felix_log_severity_screen }}",
              "vxlanEnabled": {{ calico_vxlan_mode != 'Never' }},
              "featureDetectOverride": "{{ calico_feature_detect_override }}",
              "floatingIPs": "{{ calico_felix_floating_ips }}"
            }
          }

    - name: Calico | Process FelixConfiguration
      set_fact:
        _felix_config: "{{ _felix_cmd.stdout | from_json | combine(_felix_config, recursive=True) }}"
      when:
        - _felix_cmd is success

    - name: Calico | Configure calico FelixConfiguration
      command:
        cmd: "{{ bin_dir }}/calicoctl.sh apply -f -"
        stdin: "{{ _felix_config is string | ternary(_felix_config, _felix_config | to_json) }}"
      changed_when: false

- name: Calico | Configure Calico IP Pool
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
  block:
    - name: Calico | Get existing calico network pool
      command: "{{ bin_dir }}/calicoctl.sh get ippool {{ calico_pool_name }} -o json"
      register: _calico_pool_cmd
      ignore_errors: true
      changed_when: false

    - name: Calico | Set kubespray calico network pool
      set_fact:
        _calico_pool: >
          {
            "kind": "IPPool",
            "apiVersion": "projectcalico.org/v3",
            "metadata": {
              "name": "{{ calico_pool_name }}",
            },
            "spec": {
              "blockSize": {{ calico_pool_blocksize }},
              "cidr": "{{ calico_pool_cidr | default(kube_pods_subnet) }}",
              "ipipMode": "{{ calico_ipip_mode }}",
              "vxlanMode": "{{ calico_vxlan_mode }}",
              "natOutgoing": {{ nat_outgoing | default(false) }}
            }
          }

    - name: Calico | Process calico network pool
      when:
        - _calico_pool_cmd is success
      block:
        - name: Calico | Get current calico network pool blocksize
          set_fact:
            _calico_blocksize: >
              {
                "spec": {
                  "blockSize": {{ (_calico_pool_cmd.stdout | from_json).spec.blockSize }}
                }
              }
        - name: Calico | Merge calico network pool
          set_fact:
            _calico_pool: "{{ _calico_pool_cmd.stdout | from_json | combine(_calico_pool, _calico_blocksize, recursive=True) }}"

    - name: Calico | Configure calico network pool
      command:
        cmd: "{{ bin_dir }}/calicoctl.sh apply -f -"
        stdin: "{{ _calico_pool is string | ternary(_calico_pool, _calico_pool | to_json) }}"
      changed_when: false

- name: Calico | Configure Calico IPv6 Pool
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
    - enable_dual_stack_networks | bool
  block:
    - name: Calico | Get existing calico ipv6 network pool
      command: "{{ bin_dir }}/calicoctl.sh get ippool {{ calico_pool_name }}-ipv6 -o json"
      register: _calico_pool_ipv6_cmd
      ignore_errors: true
      changed_when: false

    - name: Calico | Set kubespray calico network pool
      set_fact:
        _calico_pool_ipv6: >
          {
            "kind": "IPPool",
            "apiVersion": "projectcalico.org/v3",
            "metadata": {
              "name": "{{ calico_pool_name }}-ipv6",
            },
            "spec": {
              "blockSize": {{ calico_pool_blocksize_ipv6 }},
              "cidr": "{{ calico_pool_cidr_ipv6 | default(kube_pods_subnet_ipv6) }}",
              "ipipMode": "{{ calico_ipip_mode_ipv6 }}",
              "vxlanMode": "{{ calico_vxlan_mode_ipv6 }}",
              "natOutgoing": {{ nat_outgoing_ipv6 | default(false) }}
            }
          }

    - name: Calico | Process calico ipv6 network pool
      when:
        - _calico_pool_ipv6_cmd is success
      block:
        - name: Calico | Get current calico ipv6 network pool blocksize
          set_fact:
            _calico_blocksize_ipv6: >
              {
                "spec": {
                  "blockSize": {{ (_calico_pool_ipv6_cmd.stdout | from_json).spec.blockSize }}
                }
              }
        - name: Calico | Merge calico ipv6 network pool
          set_fact:
            _calico_pool_ipv6: "{{ _calico_pool_ipv6_cmd.stdout | from_json | combine(_calico_pool_ipv6, _calico_blocksize_ipv6, recursive=True) }}"

    - name: Calico | Configure calico ipv6 network pool
      command:
        cmd: "{{ bin_dir }}/calicoctl.sh apply -f -"
        stdin: "{{ _calico_pool_ipv6 is string | ternary(_calico_pool_ipv6, _calico_pool_ipv6 | to_json) }}"
      changed_when: false

- name: Populate Service External IPs
  set_fact:
    _service_external_ips: "{{ _service_external_ips | default([]) + [{'cidr': item}] }}"
  with_items: "{{ calico_advertise_service_external_ips }}"
  run_once: true

- name: Populate Service LoadBalancer IPs
  set_fact:
    _service_loadbalancer_ips: "{{ _service_loadbalancer_ips | default([]) + [{'cidr': item}] }}"
  with_items: "{{ calico_advertise_service_loadbalancer_ips }}"
  run_once: true

- name: "Determine nodeToNodeMesh needed state"
  set_fact:
    nodeToNodeMeshEnabled: "false"
  when:
    - peer_with_router | default(false) or peer_with_calico_rr | default(false)
    - inventory_hostname in groups['k8s_cluster']
  run_once: true

- name: Calico | Configure Calico BGP
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
  block:
    - name: Calico | Get existing BGP Configuration
      command: "{{ bin_dir }}/calicoctl.sh get bgpconfig default -o json"
      register: _bgp_config_cmd
      ignore_errors: true
      changed_when: false

    - name: Calico | Set kubespray BGP Configuration
      set_fact:
        # noqa: jinja[spacing]
        _bgp_config: >
          {
            "kind": "BGPConfiguration",
            "apiVersion": "projectcalico.org/v3",
            "metadata": {
              "name": "default",
            },
            "spec": {
              "listenPort": {{ calico_bgp_listen_port }},
              "logSeverityScreen": "Info",
              {% if not calico_no_global_as_num | default(false) %}"asNumber": {{ global_as_num }},{% endif %}
              "nodeToNodeMeshEnabled": {{ nodeToNodeMeshEnabled | default('true') }} ,
              {% if calico_advertise_cluster_ips | default(false) %}
              "serviceClusterIPs": [{"cidr": "{{ kube_service_addresses }}" } {{ ',{"cidr":"' + kube_service_addresses_ipv6 + '"}' if enable_dual_stack_networks else '' }}],{% endif %}
              {% if calico_advertise_service_loadbalancer_ips | length > 0  %}"serviceLoadBalancerIPs": {{ _service_loadbalancer_ips }},{% endif %}
              "serviceExternalIPs": {{ _service_external_ips | default([]) }}
            }
          }

    - name: Calico | Process BGP Configuration
      set_fact:
        _bgp_config: "{{ _bgp_config_cmd.stdout | from_json | combine(_bgp_config, recursive=True) }}"
      when:
        - _bgp_config_cmd is success

    - name: Calico | Set up BGP Configuration
      command:
        cmd: "{{ bin_dir }}/calicoctl.sh apply -f -"
        stdin: "{{ _bgp_config is string | ternary(_bgp_config, _bgp_config | to_json) }}"
      changed_when: false

- name: Calico | Create calico manifests
  template:
    src: "{{ item.file }}.j2"
    dest: "{{ kube_config_dir }}/{{ item.file }}"
    mode: "0644"
  with_items:
    - {name: calico-config, file: calico-config.yml, type: cm}
    - {name: calico-node, file: calico-node.yml, type: ds}
    - {name: calico, file: calico-node-sa.yml, type: sa}
    - {name: calico, file: calico-cr.yml, type: clusterrole}
    - {name: calico, file: calico-crb.yml, type: clusterrolebinding}
    - {name: kubernetes-services-endpoint, file: kubernetes-services-endpoint.yml, type: cm }
  register: calico_node_manifests
  when:
    - inventory_hostname in groups['kube_control_plane']
    - rbac_enabled or item.type not in rbac_resources

- name: Calico | Create calico manifests for typha
  template:
    src: "{{ item.file }}.j2"
    dest: "{{ kube_config_dir }}/{{ item.file }}"
    mode: "0644"
  with_items:
    - {name: calico, file: calico-typha.yml, type: typha}
  register: calico_node_typha_manifest
  when:
    - inventory_hostname in groups['kube_control_plane']
    - typha_enabled

- name: Calico | get calico apiserver caBundle
  command: "{{ bin_dir }}/kubectl get secret -n calico-apiserver calico-apiserver-certs -o jsonpath='{.data.apiserver\\.crt}'"
  changed_when: false
  register: calico_apiserver_cabundle
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
    - calico_apiserver_enabled

- name: Calico | set calico apiserver caBundle fact
  set_fact:
    calico_apiserver_cabundle: "{{ calico_apiserver_cabundle.stdout }}"
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
    - calico_apiserver_enabled

- name: Calico | Create calico manifests for apiserver
  template:
    src: "{{ item.file }}.j2"
    dest: "{{ kube_config_dir }}/{{ item.file }}"
    mode: "0644"
  with_items:
    - {name: calico, file: calico-apiserver.yml, type: calico-apiserver}
  register: calico_apiserver_manifest
  when:
    - inventory_hostname in groups['kube_control_plane']
    - calico_apiserver_enabled

- name: Start Calico resources
  kube:
    name: "{{ item.item.name }}"
    namespace: "kube-system"
    kubectl: "{{ bin_dir }}/kubectl"
    resource: "{{ item.item.type }}"
    filename: "{{ kube_config_dir }}/{{ item.item.file }}"
    state: "latest"
  with_items:
    - "{{ calico_node_manifests.results }}"
    - "{{ calico_node_typha_manifest.results }}"
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
    - not item is skipped
  loop_control:
    label: "{{ item.item.file }}"

- name: Start Calico apiserver resources
  kube:
    name: "{{ item.item.name }}"
    namespace: "calico-apiserver"
    kubectl: "{{ bin_dir }}/kubectl"
    resource: "{{ item.item.type }}"
    filename: "{{ kube_config_dir }}/{{ item.item.file }}"
    state: "latest"
  with_items:
    - "{{ calico_apiserver_manifest.results }}"
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
    - not item is skipped
  loop_control:
    label: "{{ item.item.file }}"

- name: Wait for calico kubeconfig to be created
  wait_for:
    path: /etc/cni/net.d/calico-kubeconfig
    timeout: "{{ calico_kubeconfig_wait_timeout }}"
  when:
    - inventory_hostname not in groups['kube_control_plane']
    - calico_datastore == "kdd"

- name: Calico | Create Calico ipam manifests
  template:
    src: "{{ item.file }}.j2"
    dest: "{{ kube_config_dir }}/{{ item.file }}"
    mode: "0644"
  with_items:
    - {name: calico, file: calico-ipamconfig.yml, type: ipam}
  when:
    - inventory_hostname in groups['kube_control_plane']
    - calico_datastore == "kdd"

- name: Calico | Create ipamconfig resources
  kube:
    kubectl: "{{ bin_dir }}/kubectl"
    filename: "{{ kube_config_dir }}/calico-ipamconfig.yml"
    state: "latest"
  register: resource_result
  until: resource_result is succeeded
  retries: 4
  when:
    - inventory_hostname == groups['kube_control_plane'][0]
    - calico_datastore == "kdd"

- name: Calico | Peer with Calico Route Reflector
  include_tasks: peer_with_calico_rr.yml
  when:
    - peer_with_calico_rr | default(false)

- name: Calico | Peer with the router
  include_tasks: peer_with_router.yml
  when:
    - peer_with_router | default(false)
