[stable/zed] feat: containerize openstack cli (#1173)

This is an automated cherry-pick of #972
/assign mnaser
diff --git a/images/python-openstackclient/Dockerfile b/images/python-openstackclient/Dockerfile
new file mode 100644
index 0000000..2912699
--- /dev/null
+++ b/images/python-openstackclient/Dockerfile
@@ -0,0 +1,36 @@
+# Copyright (c) 2024 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+ARG RELEASE
+
+FROM registry.atmosphere.dev/library/openstack-venv-builder:${RELEASE} AS build
+RUN --mount=type=cache,mode=0755,target=/root/.cache/pip,sharing=private <<EOF bash -xe
+pip3 install \
+    --constraint /upper-constraints.txt \
+        python-openstackclient \
+        python-barbicanclient \
+        python-designateclient \
+        python-glanceclient \
+        python-heatclient \
+        python-magnumclient \
+        python-manilaclient \
+        python-neutronclient \
+        python-octaviaclient \
+        osc-placement \
+        python-senlinclient \
+        python-swiftclient
+EOF
+
+FROM registry.atmosphere.dev/library/python-base:${RELEASE}
+COPY --from=build --link /var/lib/openstack /var/lib/openstack
diff --git a/playbooks/suspend_project.yml b/playbooks/suspend_project.yml
index 8088c49..5789a0f 100644
--- a/playbooks/suspend_project.yml
+++ b/playbooks/suspend_project.yml
@@ -32,12 +32,16 @@
     - name: Get list of regions with compute service
       changed_when: false
       run_once: true
-      ansible.builtin.shell:
+      ansible.builtin.shell: |
+        set -o posix
+        source /etc/profile.d/atmosphere.sh
         openstack endpoint list \
           --service compute \
           --interface public \
           --column Region \
           --format value
+      args:
+        executable: /bin/bash
       environment:
         OS_CLOUD: atmosphere
       register: _regions
@@ -45,13 +49,17 @@
     - name: Get list of VMs in the project in each region
       changed_when: false
       run_once: true
-      ansible.builtin.shell:
+      ansible.builtin.shell: |
+        set -o posix
+        source /etc/profile.d/atmosphere.sh
         openstack server list \
           --no-name-lookup \
           --project "{{ project_name }}" \
           --status ACTIVE \
           --column ID \
           --format value
+      args:
+        executable: /bin/bash
       environment:
         OS_AUTH_URL: "https://{{ openstack_helm_endpoints_keystone_api_host }}"
         OS_USERNAME: "admin-{{ openstack_helm_endpoints_region_name }}"
@@ -65,8 +73,12 @@
 
     - name: Suspend VMs in each region
       run_once: true
-      ansible.builtin.shell:
+      ansible.builtin.shell: |
+        set -o posix
+        source /etc/profile.d/atmosphere.sh
         openstack server suspend {{ item.1 }}
+      args:
+        executable: /bin/bash
       environment:
         OS_AUTH_URL: "https://{{ openstack_helm_endpoints_keystone_api_host }}"
         OS_USERNAME: "admin-{{ openstack_helm_endpoints_region_name }}"
diff --git a/playbooks/terminate_project.yml b/playbooks/terminate_project.yml
index e26f694..bc48719 100644
--- a/playbooks/terminate_project.yml
+++ b/playbooks/terminate_project.yml
@@ -31,12 +31,16 @@
     - name: Get list of regions with compute service
       changed_when: false
       run_once: true
-      ansible.builtin.shell:
+      ansible.builtin.shell: |
+        set -o posix
+        source /etc/profile.d/atmosphere.sh
         openstack endpoint list \
           --service compute \
           --interface public \
           --column Region \
           --format value
+      args:
+        executable: /bin/bash
       environment:
         OS_CLOUD: atmosphere
       register: _regions
diff --git a/roles/barbican/tasks/main.yml b/roles/barbican/tasks/main.yml
index 2652cf3..3894ab5 100644
--- a/roles/barbican/tasks/main.yml
+++ b/roles/barbican/tasks/main.yml
@@ -64,9 +64,13 @@
 - name: Add implied roles
   run_once: true
   ansible.builtin.shell: |
+    set -o posix
+    source /etc/profile.d/atmosphere.sh
     openstack implied role create \
       --implied-role {{ item.implies }} \
       {{ item.role }}
+  args:
+    executable: /bin/bash
   loop:
     - role: member
       implies: creator
diff --git a/roles/defaults/vars/main.yml b/roles/defaults/vars/main.yml
index 3661f61..cfd0590 100644
--- a/roles/defaults/vars/main.yml
+++ b/roles/defaults/vars/main.yml
@@ -156,6 +156,7 @@
   octavia_health_manager: "registry.atmosphere.dev/library/octavia:{{ atmosphere_release }}"
   octavia_housekeeping: "registry.atmosphere.dev/library/octavia:{{ atmosphere_release }}"
   octavia_worker: "registry.atmosphere.dev/library/octavia:{{ atmosphere_release }}"
+  openstack_cli: "registry.atmosphere.dev/library/python-openstackclient:{{ atmosphere_release }}"
   openvswitch_db_server: "registry.atmosphere.dev/library/openvswitch:{{ atmosphere_release }}"
   openvswitch_vswitchd: "registry.atmosphere.dev/library/openvswitch:{{ atmosphere_release }}"
   ovn_controller: "registry.atmosphere.dev/library/ovn-host:{{ atmosphere_release }}"
diff --git a/roles/octavia/tasks/generate_resources.yml b/roles/octavia/tasks/generate_resources.yml
index 32ee815..af7417d 100644
--- a/roles/octavia/tasks/generate_resources.yml
+++ b/roles/octavia/tasks/generate_resources.yml
@@ -85,9 +85,13 @@
 - name: Set binding for ports
   changed_when: false
   ansible.builtin.shell: |
+    set -o posix
+    source /etc/profile.d/atmosphere.sh
     openstack port set \
       --host {{ hostvars[item]['ansible_fqdn'] }} \
       octavia-health-manager-port-{{ hostvars[item]['inventory_hostname_short'] }}
+  args:
+    executable: /bin/bash
   environment:
     OS_CLOUD: atmosphere
   loop: "{{ groups['controllers'] }}"
diff --git a/roles/octavia/tasks/main.yml b/roles/octavia/tasks/main.yml
index c5d6491..a984c6b 100644
--- a/roles/octavia/tasks/main.yml
+++ b/roles/octavia/tasks/main.yml
@@ -133,9 +133,13 @@
 - name: Add implied roles
   run_once: true
   ansible.builtin.shell: |
+    set -o posix
+    source /etc/profile.d/atmosphere.sh
     openstack implied role create \
       --implied-role {{ item.implies }} \
       {{ item.role }}
+  args:
+    executable: /bin/bash
   loop:
     - role: member
       implies: load-balancer_member
diff --git a/roles/openstack_cli/defaults/main.yml b/roles/openstack_cli/defaults/main.yml
index 06e41b5..9a58d41 100644
--- a/roles/openstack_cli/defaults/main.yml
+++ b/roles/openstack_cli/defaults/main.yml
@@ -1,19 +1,18 @@
----
-# .. vim: foldmarker=[[[,]]]:foldmethod=marker
-
-# .. Copyright (C) 2022 VEXXHOST, Inc.
-# .. SPDX-License-Identifier: Apache-2.0
-
-# Default variables
-# =================
-
-# .. contents:: Sections
-#    :local:
-
-
-# .. envvar:: openstack_cli_packages [[[
+# Copyright (c) 2024 VEXXHOST, Inc.
 #
-# List of packages to install for OpenStack CLI
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# NOTE(fitbeard): This is to accomodate for the uninstallation of the old packages
 openstack_cli_packages:
   - python3-barbicanclient
   - python3-designateclient
@@ -29,7 +28,5 @@
   - python3-senlinclient
   - python3-swiftclient
 
-                                                                   # ]]]
-
 # URL for the Ubuntu Cloud Archive repository
 openstack_cli_cloud_archive_repo: deb http://ubuntu-cloud.archive.canonical.com/ubuntu {{ ansible_distribution_release }}-updates/yoga main
diff --git a/roles/openstack_cli/meta/main.yml b/roles/openstack_cli/meta/main.yml
index 9b0260e..1bda249 100644
--- a/roles/openstack_cli/meta/main.yml
+++ b/roles/openstack_cli/meta/main.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
+# Copyright (c) 2024 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -19,9 +19,17 @@
   min_ansible_version: 5.5.0
   standalone: false
   platforms:
+    - name: EL
+      versions:
+        - "8"
+        - "9"
     - name: Ubuntu
       versions:
         - focal
+        - jammy
 
 dependencies:
   - role: defaults
+  - role: vexxhost.containers.nerdctl
+    vars:
+      nerdctl_namespace: k8s.io
diff --git a/roles/openstack_cli/tasks/main.yml b/roles/openstack_cli/tasks/main.yml
index d0e8e2b..37ef184 100644
--- a/roles/openstack_cli/tasks/main.yml
+++ b/roles/openstack_cli/tasks/main.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
+# Copyright (c) 2024 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -12,22 +12,24 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- name: Install Ubuntu Cloud Archive keyring
+- name: Uninstall OpenStack client system packages
+  ansible.builtin.package:
+    name: "{{ openstack_cli_packages }}"
+    state: absent
+  when: ansible_facts['os_family'] in ['Debian']
+
+- name: Uninstall Ubuntu Cloud Archive keyring
   ansible.builtin.apt:
     name: ubuntu-cloud-keyring
-    state: present
+    state: absent
+  when: ansible_facts['os_family'] in ['Debian']
 
-- name: Add latest Ubuntu Cloud Archive repository
+- name: Remove Ubuntu Cloud Archive repository
   ansible.builtin.apt_repository:
     filename: ubuntu-cloud-archive
     repo: "{{ openstack_cli_cloud_archive_repo }}"
-    state: present
-  when: ansible_distribution_release == "focal"
-
-- name: Install OpenStack client
-  become: true
-  ansible.builtin.apt:
-    name: "{{ openstack_cli_packages }}"
+    state: absent
+  when: ansible_facts['os_family'] in ['Debian']
 
 - name: Generate OpenStack-Helm endpoints
   ansible.builtin.include_role:
@@ -43,3 +45,12 @@
     owner: root
     group: root
     mode: "0600"
+
+- name: Generate openstack aliases
+  become: true
+  ansible.builtin.template:
+    src: atmosphere.sh.j2
+    dest: /etc/profile.d/atmosphere.sh
+    owner: root
+    group: root
+    mode: "0644"
diff --git a/roles/openstack_cli/templates/atmosphere.sh.j2 b/roles/openstack_cli/templates/atmosphere.sh.j2
new file mode 100644
index 0000000..416a825
--- /dev/null
+++ b/roles/openstack_cli/templates/atmosphere.sh.j2
@@ -0,0 +1,14 @@
+alias osc='nerdctl run --rm --network host \
+      --volume $PWD:/opt --volume /tmp:/tmp \
+      --volume /etc/openstack:/etc/openstack:ro \
+{% if cluster_issuer_type is defined and cluster_issuer_type in ('self-signed', 'ca') %}
+      --volume {{ '/usr/local/share/ca-certificates/atmosphere.crt:/usr/local/share/ca-certificates/atmosphere.crt:ro' if ansible_facts['os_family']
+      in ['Debian'] else '/etc/pki/ca-trust/source/anchors/atmosphere.crt:/etc/pki/ca-trust/source/anchors/atmosphere.crt:ro' }} \
+{% endif %}
+      --env-file <(env | grep OS_) \
+      {{ atmosphere_images['openstack_cli'] }}'
+alias openstack='osc openstack'
+alias nova='osc nova'
+alias neutron='osc neutron'
+alias cinder='osc cinder'
+alias glance='osc glance'
diff --git a/roles/openstack_cli/templates/openrc.j2 b/roles/openstack_cli/templates/openrc.j2
index 188fb81..87e5e34 100644
--- a/roles/openstack_cli/templates/openrc.j2
+++ b/roles/openstack_cli/templates/openrc.j2
@@ -12,5 +12,5 @@
 export OS_PROJECT_NAME=admin
 
 {% if cluster_issuer_type is defined and cluster_issuer_type in ('self-signed', 'ca') %}
-export OS_CACERT=/usr/local/share/ca-certificates/atmosphere.crt
+export OS_CACERT={{ '/usr/local/share/ca-certificates' if ansible_facts['os_family'] in ['Debian'] else '/etc/pki/ca-trust/source/anchors' }}/atmosphere.crt
 {% endif %}
diff --git a/roles/openstacksdk/defaults/main.yml b/roles/openstacksdk/defaults/main.yml
new file mode 100644
index 0000000..27dc31a
--- /dev/null
+++ b/roles/openstacksdk/defaults/main.yml
@@ -0,0 +1,15 @@
+# Copyright (c) 2024 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+openstacksdk_version: "0.61.0"
diff --git a/roles/openstacksdk/tasks/main.yml b/roles/openstacksdk/tasks/main.yml
index 724f291..1a4c3b4 100644
--- a/roles/openstacksdk/tasks/main.yml
+++ b/roles/openstacksdk/tasks/main.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
+# Copyright (c) 2024 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -14,7 +14,8 @@
 
 - name: Install openstacksdk
   ansible.builtin.pip:
-    name: openstacksdk==0.61.0
+    name: openstacksdk
+    version: "{{ openstacksdk_version }}"
 
 - name: Create openstack config directory
   become: true
@@ -32,4 +33,4 @@
     dest: /etc/openstack/clouds.yaml
     owner: root
     group: root
-    mode: '0600'
+    mode: "0600"
diff --git a/roles/openstacksdk/templates/clouds.yaml.j2 b/roles/openstacksdk/templates/clouds.yaml.j2
index b855a63..efb2245 100644
--- a/roles/openstacksdk/templates/clouds.yaml.j2
+++ b/roles/openstacksdk/templates/clouds.yaml.j2
@@ -9,5 +9,5 @@
       project_domain_name: Default
     region_name: "{{ openstack_helm_endpoints_keystone_region_name }}"
 {% if cluster_issuer_type is defined and cluster_issuer_type in ('self-signed', 'ca') %}
-    cacert: /usr/local/share/ca-certificates/atmosphere.crt
+    cacert: "{{ '/usr/local/share/ca-certificates' if ansible_facts['os_family'] in ['Debian'] else '/etc/pki/ca-trust/source/anchors' }}/atmosphere.crt"
 {% endif %}
diff --git a/zuul.d/container-images/python-openstackclient.yaml b/zuul.d/container-images/python-openstackclient.yaml
new file mode 100644
index 0000000..09475df
--- /dev/null
+++ b/zuul.d/container-images/python-openstackclient.yaml
@@ -0,0 +1,76 @@
+# Copyright (c) 2024 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- project:
+    check:
+      jobs:
+        - atmosphere-build-container-image-python-openstackclient
+    gate:
+      jobs:
+        - atmosphere-upload-container-image-python-openstackclient
+    promote:
+      jobs:
+        - atmosphere-promote-container-image-python-openstackclient
+
+- job:
+    name: atmosphere-build-container-image-python-openstackclient
+    parent: atmosphere-build-container-image
+    dependencies:
+      - name: atmosphere-build-container-image-ubuntu
+        soft: true
+      - name: atmosphere-build-container-image-ubuntu-cloud-archive
+        soft: true
+      - name: atmosphere-build-container-image-python-base
+        soft: true
+      - name: atmosphere-build-container-image-openstack-venv-builder
+        soft: true
+    vars: &container_image_vars
+      promote_container_image_job: atmosphere-upload-container-image-python-openstackclient
+      container_images:
+        - context: images/python-openstackclient
+          registry: registry.atmosphere.dev
+          repository: registry.atmosphere.dev/library/python-openstackclient
+          arch:
+            - linux/amd64
+          build_args:
+            - "RELEASE={{ zuul.branch | replace('stable/', '') }}"
+          tags:
+            - "{{ zuul.branch | replace('stable/', '') }}"
+    files: &container_image_files
+      - images/ubuntu/.*
+      - images/ubuntu-cloud-archive/.*
+      - images/python-base/.*
+      - images/openstack-venv-builder/.*
+      - images/python-openstackclient/.*
+
+- job:
+    name: atmosphere-upload-container-image-python-openstackclient
+    parent: atmosphere-upload-container-image
+    dependencies:
+      - name: atmosphere-upload-container-image-ubuntu
+        soft: true
+      - name: atmosphere-upload-container-image-ubuntu-cloud-archive
+        soft: true
+      - name: atmosphere-upload-container-image-python-base
+        soft: true
+      - name: atmosphere-upload-container-image-openstack-venv-builder
+        soft: true
+    vars: *container_image_vars
+    files: *container_image_files
+
+- job:
+    name: atmosphere-promote-container-image-python-openstackclient
+    parent: atmosphere-promote-container-image
+    vars: *container_image_vars
+    files: *container_image_files