feat: Set retries of helmreleases (#253)

diff --git a/atmosphere/operator/api/types.py b/atmosphere/operator/api/types.py
index 8e99769..1a44267 100644
--- a/atmosphere/operator/api/types.py
+++ b/atmosphere/operator/api/types.py
@@ -124,6 +124,10 @@
     spec: HelmChartTemplateSpec
 
 
+class HelmReleaseActionRemediation(pydantic.BaseModel):
+    retries: int = 3
+
+
 class HelmReleaseActionSpecCRDsPolicy(str, Enum):
     SKIP = "Skip"
     CREATE = "Create"
@@ -135,6 +139,7 @@
         HelmReleaseActionSpecCRDsPolicy.CREATE_REPLACE
     )
     disable_wait: bool = pydantic.Field(default=True, alias="disableWait")
+    remediation: HelmReleaseActionRemediation = HelmReleaseActionRemediation()
 
     class Config:
         allow_population_by_field_name = True
diff --git a/atmosphere/operator/tasks.py b/atmosphere/operator/tasks.py
index 68fcc50..c089e63 100644
--- a/atmosphere/operator/tasks.py
+++ b/atmosphere/operator/tasks.py
@@ -120,10 +120,16 @@
                     "install": {
                         "crds": "CreateReplace",
                         "disableWait": True,
+                        "remediation": {
+                            "retries": 3,
+                        },
                     },
                     "upgrade": {
                         "crds": "CreateReplace",
                         "disableWait": True,
+                        "remediation": {
+                            "retries": 3,
+                        },
                     },
                     "values": values,
                     "valuesFrom": values_from,
diff --git a/atmosphere/tasks/kubernetes/flux.py b/atmosphere/tasks/kubernetes/flux.py
index 3294897..9b4c3af 100644
--- a/atmosphere/tasks/kubernetes/flux.py
+++ b/atmosphere/tasks/kubernetes/flux.py
@@ -66,10 +66,16 @@
                     "install": {
                         "crds": "CreateReplace",
                         "disableWait": True,
+                        "remediation": {
+                            "retries": 3,
+                        },
                     },
                     "upgrade": {
                         "crds": "CreateReplace",
                         "disableWait": True,
+                        "remediation": {
+                            "retries": 3,
+                        },
                     },
                     "values": self._values,
                     "valuesFrom": self._values_from,
diff --git a/atmosphere/tests/unit/operator/test_objects.py b/atmosphere/tests/unit/operator/test_objects.py
index 017166e..a2fbbcd 100644
--- a/atmosphere/tests/unit/operator/test_objects.py
+++ b/atmosphere/tests/unit/operator/test_objects.py
@@ -193,11 +193,17 @@
                     "install": {
                         "crds": "CreateReplace",
                         "disableWait": True,
+                        "remediation": {
+                            "retries": 3,
+                        },
                     },
                     "interval": "60s",
                     "upgrade": {
                         "crds": "CreateReplace",
                         "disableWait": True,
+                        "remediation": {
+                            "retries": 3,
+                        },
                     },
                     "values": {
                         "foo": "bar",
diff --git a/atmosphere/tests/unit/operator/test_types.py b/atmosphere/tests/unit/operator/test_types.py
index 446b31a..8acf233 100644
--- a/atmosphere/tests/unit/operator/test_types.py
+++ b/atmosphere/tests/unit/operator/test_types.py
@@ -107,6 +107,13 @@
         assert isinstance(instance.spec, types.HelmChartTemplateSpec)
 
 
+class HelmReleaseActionRemediation:
+    @given(st.builds(types.HelmReleaseActionRemediation))
+    def test_property(self, instance):
+        assert isinstance(instance, types.HelmReleaseActionRemediation)
+        assert isinstance(instance.retries, int)
+
+
 class TestHelmReleaseActionSpec:
     @given(st.builds(types.HelmReleaseActionSpec))
     def test_property(self, instance):
@@ -114,6 +121,7 @@
         assert isinstance(instance.crds, types.HelmReleaseActionSpecCRDsPolicy)
         assert isinstance(instance.disable_wait, bool)
         assert instance.disable_wait in [True, False]
+        assert isinstance(instance.remediation, types.HelmReleaseActionRemediation)
 
 
 class TestHelmReleaseValuesReference:
diff --git a/roles/openstack_helm_octavia/tasks/main.yml b/roles/openstack_helm_octavia/tasks/main.yml
index 14001e1..dc34a2a 100644
--- a/roles/openstack_helm_octavia/tasks/main.yml
+++ b/roles/openstack_helm_octavia/tasks/main.yml
@@ -84,7 +84,7 @@
 
 - name: Set controller_ip_port_list
   ansible.builtin.set_fact:
-    _openstack_helm_octavia_controller_ip_port_list: "{{ _openstack_helm_octavia_controller_ip_port_list | d([]) + [item.openstack_ports[0].fixed_ips[0].ip_address + ':5555'] }}"
+    _openstack_helm_octavia_controller_ip_port_list: "{{ (_openstack_helm_octavia_controller_ip_port_list | d([]) + [item.openstack_ports[0].fixed_ips[0].ip_address + ':5555']) | unique }}"
   loop: "{{ _openstack_helm_octavia_health_manager_ports.results }}"
   loop_control:
     label: "{{ item.openstack_ports[0].name }}"
diff --git a/roles/openstack_helm_octavia/vars/main.yml b/roles/openstack_helm_octavia/vars/main.yml
index 7f320e3..526514f 100644
--- a/roles/openstack_helm_octavia/vars/main.yml
+++ b/roles/openstack_helm_octavia/vars/main.yml
@@ -118,7 +118,7 @@
         client_cert: /etc/octavia/certs/client/tls-combined.pem
         server_ca: /etc/octavia/certs/server/ca.crt
       health_manager:
-        controller_ip_port_list: "{{ _openstack_helm_octavia_controller_ip_port_list | join(',') }}"
+        controller_ip_port_list: "{{ _openstack_helm_octavia_controller_ip_port_list | sort | join(',') }}"
         heartbeat_key: "{{ openstack_helm_octavia_heartbeat_key }}"
       oslo_messaging_notifications:
         driver: noop