From 63d32b632d3cc959ce439eea00efe96a4126ca21 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Wed, 30 Oct 2024 15:31:36 -0700 Subject: [PATCH 1/5] Add base ubuntu 20.04 image for testing --- custom/testing/ubuntu-20.04.Dockerfile | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 custom/testing/ubuntu-20.04.Dockerfile diff --git a/custom/testing/ubuntu-20.04.Dockerfile b/custom/testing/ubuntu-20.04.Dockerfile new file mode 100644 index 0000000..88fbc82 --- /dev/null +++ b/custom/testing/ubuntu-20.04.Dockerfile @@ -0,0 +1,9 @@ +FROM ubuntu:20.04 + +COPY 01_nodoc /etc/dpkg/dpkg.cfg.d/01_nodoc + +RUN apt update -y \ + && echo 'tzdata tzdata/Areas select America' | debconf-set-selections \ + && echo 'tzdata tzdata/Zones/America select Phoenix' | debconf-set-selections \ + && DEBIAN_FRONTEND="noninteractive" apt install -y \ + python3 python3-venv python3-pip From b32e6f5c2d4fcb4873a2aed3d26067c4eadd022c Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Wed, 30 Oct 2024 15:40:56 -0700 Subject: [PATCH 2/5] Add testing containers workflow --- .github/workflows/testing-containers.yml | 232 +++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 .github/workflows/testing-containers.yml diff --git a/.github/workflows/testing-containers.yml b/.github/workflows/testing-containers.yml new file mode 100644 index 0000000..c04e2ea --- /dev/null +++ b/.github/workflows/testing-containers.yml @@ -0,0 +1,232 @@ +name: "Salt Testing 🐳 Container" + +on: + workflow_dispatch: + schedule: + - cron: "0 19 * * *" + push: + branches: + - '*' + paths: + - containers.yml + - '.github/workflows/testing-containers.yml' + - 'custom/testing/*' + pull_request: + paths: + - containers.yml + - '.github/workflows/testing-containers.yml' + - 'custom/testing/*' + +env: + COLUMNS: 190 + PATH_IN_REPO: custom/testing + + +concurrency: + # New builds always cancel previous, still running, builds + group: custom/testing-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + matrix-generator: + name: Generate Matrix + runs-on: ubuntu-latest + outputs: + tags: ${{ steps.set-matrix.outputs.tags }} + name: ${{ steps.set-matrix.outputs.name }} + dockerinfo: ${{ steps.set-matrix.outputs.dockerinfo }} + steps: + - name: "Throttle Builds" + run: | + t="$(shuf -i 5-30 -n 1)"; echo "Sleeping $t seconds"; sleep "$t" + + - name: "Fetching Repository Contents" + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Requirements + run: | + python -m pip install -r requirements.txt + + - name: Show tools version + run: | + tools --debug --version + + - name: "Generate Matrix Data" + id: set-matrix + run: | + tools ci matrix ${{ env.PATH_IN_REPO }} + + build: + runs-on: ubuntu-latest + needs: matrix-generator + name: "Build ${{ matrix.dockerinfo.name }}:${{ matrix.dockerinfo.tag }}${{ matrix.dockerinfo.platform && format(' ({0})', matrix.dockerinfo.platform) || ''}}" + strategy: + fail-fast: false + max-parallel: 10 + matrix: + dockerinfo: ${{ fromJson(needs.matrix-generator.outputs.dockerinfo) }} + + permissions: + actions: read + checks: write + issues: read + packages: write + pull-requests: read + repository-projects: read + statuses: read + + steps: + - name: "Fetching Repository Contents" + uses: actions/checkout@v4 + + - name: "Throttle concurrent pushes" + run: | + t="$(shuf -i 5-30 -n 1)"; echo "Sleeping $t seconds"; sleep "$t" + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/${{ github.repository }}/${{ matrix.dockerinfo.name }} + tags: | + type=raw,value=${{ matrix.dockerinfo.tag }} + flavor: | + latest=false + + - name: "Docker QEMU" + uses: docker/setup-qemu-action@v3 + + - name: "Docker BuildX" + id: buildx + uses: docker/setup-buildx-action@v3 + + - name: Inspect builder + run: | + echo "Name: ${{ steps.buildx.outputs.name }}" + echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}" + echo "Status: ${{ steps.buildx.outputs.status }}" + echo "Flags: ${{ steps.buildx.outputs.flags }}" + echo "Platforms: ${{ steps.buildx.outputs.platforms }}" + + - name: "Log into GitHub Container Registry" + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: "Build & Publish" + uses: docker/build-push-action@v5 + id: build + with: + file: ${{ matrix.dockerinfo.file }} + context: ${{ env.PATH_IN_REPO }} + platforms: ${{ matrix.dockerinfo.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=ghcr.io/${{ github.repository }}/${{ matrix.dockerinfo.name }},push-by-digest=true,name-canonical=true,push=${{ + github.repository == 'saltstack/salt-ci-containers' && contains(fromJSON('["push", "schedule", "workflow_dispatch"]'), github.event_name) }} + + - name: Export digest + if: ${{ github.repository == 'saltstack/salt-ci-containers' && contains(fromJSON('["push", "schedule", "workflow_dispatch"]'), github.event_name) }} + run: | + mkdir -p /tmp/digests + touch "/tmp/digests/$(echo ${{ steps.build.outputs.digest }} | cut -d ':' -f 2)" + ls -lah /tmp/digests + + - name: Upload digest + if: ${{ github.repository == 'saltstack/salt-ci-containers' && contains(fromJSON('["push", "schedule", "workflow_dispatch"]'), github.event_name) }} + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.dockerinfo.name }}-${{ matrix.dockerinfo.tag }}-${{ matrix.dockerinfo.platform_slug }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + name: "Merge ${{ needs.matrix-generator.outputs.name }}:${{ matrix.tag }}" + if: ${{ github.repository == 'saltstack/salt-ci-containers' && contains(fromJSON('["push", "schedule", "workflow_dispatch"]'), github.event_name) }} + + strategy: + fail-fast: false + max-parallel: 10 + matrix: + tag: ${{ fromJson(needs.matrix-generator.outputs.tags) }} + + needs: + - matrix-generator + - build + + permissions: + actions: read + checks: write + issues: read + packages: write + pull-requests: read + repository-projects: read + statuses: read + + steps: + + - name: "Fetching Repository Contents" + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Requirements + run: | + python -m pip install -r requirements.txt + + - name: Show tools version + run: | + tools --debug --version + + - name: Download digests + uses: actions/download-artifact@v4 + with: + pattern: digests-${{ needs.matrix-generator.outputs.name }}-${{ matrix.tag }}-* + merge-multiple: true + path: /tmp/digests + + - name: Show digests + run: | + tree -a /tmp/digests + cat /tmp/digests/* + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/${{ github.repository }}/${{ needs.matrix-generator.outputs.name }} + tags: | + type=raw,value=${{ matrix.tag }} + flavor: | + latest=false + + - name: "Log into GitHub Container Registry" + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create manifest list and push + run: | + tools ci create-manifest-list-and-push ghcr.io/${{ github.repository }}/${{ needs.matrix-generator.outputs.name }} /tmp/digests + + - name: Inspect image + run: | + docker buildx imagetools inspect ghcr.io/${{ github.repository }}/${{ needs.matrix-generator.outputs.name }}:${{ steps.meta.outputs.version }} From 45231846dfada7d62f6b138d8fed301e0e74eebd Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Wed, 30 Oct 2024 16:16:35 -0700 Subject: [PATCH 3/5] Add container to containers.yml --- containers.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/containers.yml b/containers.yml index 8e4cc87..aa59ead 100644 --- a/containers.yml +++ b/containers.yml @@ -42,6 +42,11 @@ custom: - rockylinux-9 - ubuntu-22.04 - ubuntu-24.04 + + Salt Testing: + name: testing + versions: + - ubuntu-20.04 # <---- Custom Containers --------------------------------------------------------------------------------- # ----- Mirrored Containers ------------------------------------------------------------------------------> From bbed81fc3a230847d7e957872ca2787beb23e1c4 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Wed, 30 Oct 2024 16:24:26 -0700 Subject: [PATCH 4/5] Fix 20.04 docker image --- custom/testing/ubuntu-20.04.Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom/testing/ubuntu-20.04.Dockerfile b/custom/testing/ubuntu-20.04.Dockerfile index 88fbc82..1411e95 100644 --- a/custom/testing/ubuntu-20.04.Dockerfile +++ b/custom/testing/ubuntu-20.04.Dockerfile @@ -1,7 +1,5 @@ FROM ubuntu:20.04 -COPY 01_nodoc /etc/dpkg/dpkg.cfg.d/01_nodoc - RUN apt update -y \ && echo 'tzdata tzdata/Areas select America' | debconf-set-selections \ && echo 'tzdata tzdata/Zones/America select Phoenix' | debconf-set-selections \ From 8f8595f6745733e0c4fe89c16205244714a8f589 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Wed, 30 Oct 2024 16:27:10 -0700 Subject: [PATCH 5/5] Fix pre-commit --- .github/workflows/amazonlinux-containers.yml | 2 +- .github/workflows/archlinux-containers.yml | 2 +- .github/workflows/builddocs-containers.yml | 2 +- .github/workflows/busybox-containers.yml | 2 +- .github/workflows/consul-containers.yml | 2 +- .github/workflows/debian-containers.yml | 2 +- .github/workflows/etcd-v2-containers.yml | 2 +- .github/workflows/etcd-v3-containers.yml | 2 +- .github/workflows/fedora-containers.yml | 2 +- .github/workflows/mariadb-containers.yml | 2 +- .github/workflows/mysql-server-containers.yml | 2 +- .github/workflows/opensuse-containers.yml | 2 +- .github/workflows/percona-containers.yml | 2 +- .github/workflows/photon-containers.yml | 2 +- .github/workflows/python-containers.yml | 2 +- .github/workflows/rabbitmq-containers.yml | 2 +- .github/workflows/redis-containers.yml | 2 +- .github/workflows/rockylinux-containers.yml | 2 +- .github/workflows/testing-containers.yml | 2 +- .github/workflows/tinyproxy-containers.yml | 2 +- .github/workflows/ubuntu-containers.yml | 2 +- .github/workflows/vault-containers.yml | 2 +- .github/workflows/virt-minion-containers.yml | 2 +- .github/workflows/zookeeper-containers.yml | 2 +- README.md | 5 +++++ custom/testing/README.md | 3 +++ 26 files changed, 32 insertions(+), 24 deletions(-) create mode 100644 custom/testing/README.md diff --git a/.github/workflows/amazonlinux-containers.yml b/.github/workflows/amazonlinux-containers.yml index 2683d3a..6a01714 100644 --- a/.github/workflows/amazonlinux-containers.yml +++ b/.github/workflows/amazonlinux-containers.yml @@ -3,7 +3,7 @@ name: "Amazon Linux 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 16 * * *" + - cron: "0 15 * * *" push: branches: - '*' diff --git a/.github/workflows/archlinux-containers.yml b/.github/workflows/archlinux-containers.yml index 23630d7..8ab5ae5 100644 --- a/.github/workflows/archlinux-containers.yml +++ b/.github/workflows/archlinux-containers.yml @@ -3,7 +3,7 @@ name: "ArchLinux 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 14 * * *" + - cron: "0 13 * * *" push: branches: - '*' diff --git a/.github/workflows/builddocs-containers.yml b/.github/workflows/builddocs-containers.yml index 4aae279..e3e90c9 100644 --- a/.github/workflows/builddocs-containers.yml +++ b/.github/workflows/builddocs-containers.yml @@ -3,7 +3,7 @@ name: "builddocs 🐳 Container" on: workflow_dispatch: schedule: - - cron: "0 17 * * *" + - cron: "0 16 * * *" push: branches: - '*' diff --git a/.github/workflows/busybox-containers.yml b/.github/workflows/busybox-containers.yml index 76943d2..88238d6 100644 --- a/.github/workflows/busybox-containers.yml +++ b/.github/workflows/busybox-containers.yml @@ -3,7 +3,7 @@ name: "BusyBox 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 13 * * *" + - cron: "0 12 * * *" push: branches: - '*' diff --git a/.github/workflows/consul-containers.yml b/.github/workflows/consul-containers.yml index 2b07d10..598f8c3 100644 --- a/.github/workflows/consul-containers.yml +++ b/.github/workflows/consul-containers.yml @@ -3,7 +3,7 @@ name: "Consul 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 12 * * *" + - cron: "0 11 * * *" push: branches: - '*' diff --git a/.github/workflows/debian-containers.yml b/.github/workflows/debian-containers.yml index 9cbc32b..c0c0e5d 100644 --- a/.github/workflows/debian-containers.yml +++ b/.github/workflows/debian-containers.yml @@ -3,7 +3,7 @@ name: "Debian 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 11 * * *" + - cron: "0 10 * * *" push: branches: - '*' diff --git a/.github/workflows/etcd-v2-containers.yml b/.github/workflows/etcd-v2-containers.yml index 382e0e9..da7acc0 100644 --- a/.github/workflows/etcd-v2-containers.yml +++ b/.github/workflows/etcd-v2-containers.yml @@ -3,7 +3,7 @@ name: "Etcd v2 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 10 * * *" + - cron: "0 9 * * *" push: branches: - '*' diff --git a/.github/workflows/etcd-v3-containers.yml b/.github/workflows/etcd-v3-containers.yml index f790b34..debf06a 100644 --- a/.github/workflows/etcd-v3-containers.yml +++ b/.github/workflows/etcd-v3-containers.yml @@ -3,7 +3,7 @@ name: "Etcd v3 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 9 * * *" + - cron: "0 8 * * *" push: branches: - '*' diff --git a/.github/workflows/fedora-containers.yml b/.github/workflows/fedora-containers.yml index 0b88fb2..a0887a7 100644 --- a/.github/workflows/fedora-containers.yml +++ b/.github/workflows/fedora-containers.yml @@ -3,7 +3,7 @@ name: "Fedora 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 8 * * *" + - cron: "0 7 * * *" push: branches: - '*' diff --git a/.github/workflows/mariadb-containers.yml b/.github/workflows/mariadb-containers.yml index 22ba169..2035d2f 100644 --- a/.github/workflows/mariadb-containers.yml +++ b/.github/workflows/mariadb-containers.yml @@ -3,7 +3,7 @@ name: "MariaDB 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 7 * * *" + - cron: "0 6 * * *" push: branches: - '*' diff --git a/.github/workflows/mysql-server-containers.yml b/.github/workflows/mysql-server-containers.yml index e02f034..0d42567 100644 --- a/.github/workflows/mysql-server-containers.yml +++ b/.github/workflows/mysql-server-containers.yml @@ -3,7 +3,7 @@ name: "MySQL Server 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 6 * * *" + - cron: "0 5 * * *" push: branches: - '*' diff --git a/.github/workflows/opensuse-containers.yml b/.github/workflows/opensuse-containers.yml index 58ecf1f..12035d5 100644 --- a/.github/workflows/opensuse-containers.yml +++ b/.github/workflows/opensuse-containers.yml @@ -3,7 +3,7 @@ name: "Opensuse 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 5 * * *" + - cron: "0 4 * * *" push: branches: - '*' diff --git a/.github/workflows/percona-containers.yml b/.github/workflows/percona-containers.yml index b4846e7..c6144f6 100644 --- a/.github/workflows/percona-containers.yml +++ b/.github/workflows/percona-containers.yml @@ -3,7 +3,7 @@ name: "Percona 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 4 * * *" + - cron: "0 3 * * *" push: branches: - '*' diff --git a/.github/workflows/photon-containers.yml b/.github/workflows/photon-containers.yml index ee5a1ab..8c9a82e 100644 --- a/.github/workflows/photon-containers.yml +++ b/.github/workflows/photon-containers.yml @@ -3,7 +3,7 @@ name: "Photon 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 3 * * *" + - cron: "0 2 * * *" push: branches: - '*' diff --git a/.github/workflows/python-containers.yml b/.github/workflows/python-containers.yml index 412b134..53aa288 100644 --- a/.github/workflows/python-containers.yml +++ b/.github/workflows/python-containers.yml @@ -3,7 +3,7 @@ name: "Python 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 2 * * *" + - cron: "0 1 * * *" push: branches: - '*' diff --git a/.github/workflows/rabbitmq-containers.yml b/.github/workflows/rabbitmq-containers.yml index 3b578b6..d2af278 100644 --- a/.github/workflows/rabbitmq-containers.yml +++ b/.github/workflows/rabbitmq-containers.yml @@ -3,7 +3,7 @@ name: "RabbitMQ 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 1 * * *" + - cron: "0 0 * * *" push: branches: - '*' diff --git a/.github/workflows/redis-containers.yml b/.github/workflows/redis-containers.yml index 01d78ec..74eeb90 100644 --- a/.github/workflows/redis-containers.yml +++ b/.github/workflows/redis-containers.yml @@ -3,7 +3,7 @@ name: "Redis 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 0 * * *" + - cron: "0 23 * * *" push: branches: - '*' diff --git a/.github/workflows/rockylinux-containers.yml b/.github/workflows/rockylinux-containers.yml index 6f66f2f..3edc171 100644 --- a/.github/workflows/rockylinux-containers.yml +++ b/.github/workflows/rockylinux-containers.yml @@ -3,7 +3,7 @@ name: "RockyLinux 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 23 * * *" + - cron: "0 22 * * *" push: branches: - '*' diff --git a/.github/workflows/testing-containers.yml b/.github/workflows/testing-containers.yml index c04e2ea..ebd1bc2 100644 --- a/.github/workflows/testing-containers.yml +++ b/.github/workflows/testing-containers.yml @@ -3,7 +3,7 @@ name: "Salt Testing 🐳 Container" on: workflow_dispatch: schedule: - - cron: "0 19 * * *" + - cron: "0 18 * * *" push: branches: - '*' diff --git a/.github/workflows/tinyproxy-containers.yml b/.github/workflows/tinyproxy-containers.yml index 69afdf7..f466b94 100644 --- a/.github/workflows/tinyproxy-containers.yml +++ b/.github/workflows/tinyproxy-containers.yml @@ -3,7 +3,7 @@ name: "Tinyproxy 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 22 * * *" + - cron: "0 21 * * *" push: branches: - '*' diff --git a/.github/workflows/ubuntu-containers.yml b/.github/workflows/ubuntu-containers.yml index c1af01c..3c4bdd1 100644 --- a/.github/workflows/ubuntu-containers.yml +++ b/.github/workflows/ubuntu-containers.yml @@ -3,7 +3,7 @@ name: "Ubuntu 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 21 * * *" + - cron: "0 20 * * *" push: branches: - '*' diff --git a/.github/workflows/vault-containers.yml b/.github/workflows/vault-containers.yml index 62efb8d..c350f9e 100644 --- a/.github/workflows/vault-containers.yml +++ b/.github/workflows/vault-containers.yml @@ -3,7 +3,7 @@ name: "Vault 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 20 * * *" + - cron: "0 19 * * *" push: branches: - '*' diff --git a/.github/workflows/virt-minion-containers.yml b/.github/workflows/virt-minion-containers.yml index a4ee2f6..0486db1 100644 --- a/.github/workflows/virt-minion-containers.yml +++ b/.github/workflows/virt-minion-containers.yml @@ -3,7 +3,7 @@ name: "Virt Minion 🐳 Container" on: workflow_dispatch: schedule: - - cron: "0 18 * * *" + - cron: "0 17 * * *" push: branches: - '*' diff --git a/.github/workflows/zookeeper-containers.yml b/.github/workflows/zookeeper-containers.yml index 8d0736b..a1af6e8 100644 --- a/.github/workflows/zookeeper-containers.yml +++ b/.github/workflows/zookeeper-containers.yml @@ -3,7 +3,7 @@ name: "Apache ZooKeeper 🐳 Mirror" on: workflow_dispatch: schedule: - - cron: "0 15 * * *" + - cron: "0 14 * * *" push: branches: - '*' diff --git a/README.md b/README.md index 2ccaad9..136c2f2 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,11 @@ will be commited. - packaging:ubuntu-24.04 - `ghcr.io/saltstack/salt-ci-containers/packaging:ubuntu-24.04` +### [![Salt Testing](https://github.com/saltstack/salt-ci-containers/actions/workflows/testing-containers.yml/badge.svg)](https://github.com/saltstack/salt-ci-containers/actions/workflows/testing-containers.yml) + +- testing:ubuntu-20.04 - `ghcr.io/saltstack/salt-ci-containers/testing:ubuntu-20.04` + + ### [![Virt Minion](https://github.com/saltstack/salt-ci-containers/actions/workflows/virt-minion-containers.yml/badge.svg)](https://github.com/saltstack/salt-ci-containers/actions/workflows/virt-minion-containers.yml) - virt-minion:latest - `ghcr.io/saltstack/salt-ci-containers/virt-minion:latest` diff --git a/custom/testing/README.md b/custom/testing/README.md new file mode 100644 index 0000000..4642061 --- /dev/null +++ b/custom/testing/README.md @@ -0,0 +1,3 @@ +# [![Salt Testing](https://github.com/saltstack/salt-ci-containers/actions/workflows/testing-containers.yml/badge.svg)](https://github.com/saltstack/salt-ci-containers/actions/workflows/testing-containers.yml) + +- testing:ubuntu-20.04 - `ghcr.io/saltstack/salt-ci-containers/testing:ubuntu-20.04`