Compare commits

..

1 Commits

Author SHA1 Message Date
GitHub Action 6f7c489cec build 2025-08-06 17:20:35 +00:00
83 changed files with 27013 additions and 31224 deletions
-53
View File
@@ -1,53 +0,0 @@
name: 'Setup Minikube Test Environment'
description: 'Common setup steps for minikube integration tests'
inputs:
install-smi:
description: 'Install Linkerd SMI for service mesh tests'
required: false
default: 'false'
runs:
using: 'composite'
steps:
- name: Install dependencies
shell: bash
run: |
rm -rf node_modules/
npm install
- name: Build
shell: bash
run: npm run build
- name: Install conntrack
shell: bash
run: sudo apt-get install -y conntrack
- uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
name: Install Kubectl
- id: setup-minikube
name: Setup Minikube
uses: medyagh/setup-minikube@e9e035a86bbc3caea26a450bd4dbf9d0c453682e # v0.0.21
with:
minikube-version: 1.37.0
kubernetes-version: 1.31.0
driver: 'docker'
- name: Install Linkerd and SMI
if: inputs.install-smi == 'true'
shell: bash
run: |
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install-edge | sh
export PATH=$PATH:/home/runner/.linkerd2/bin
curl -sL https://linkerd.github.io/linkerd-smi/install | sh
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
linkerd install --crds | kubectl apply -f -
linkerd install --set proxyInit.runAsRoot=true | kubectl apply -f -
linkerd smi install | kubectl apply -f -
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # 6.1.0
name: Install Python
with:
python-version: '3.x'
+4 -4
View File
@@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -24,7 +24,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 #v3.29.5
uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d #v3.29.5
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@@ -32,7 +32,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 #v3.29.5
uses: github/codeql-action/autobuild@51f77329afa6477de8c49fc9c7046c15b9a4e79d #v3.29.5
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -46,4 +46,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 #v3.29.5
uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d #v3.29.5
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
name: Setting issue as idle
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -24,7 +24,7 @@ jobs:
operations-per-run: 100
exempt-issue-labels: 'backlog'
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
name: Setting PR as idle
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: install deps
run: npm install
@@ -18,15 +18,39 @@ jobs:
KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup
name: Setup Minikube Environment
timeout-minutes: 5
- name: Install dependencies
run: |
rm -rf node_modules/
npm install
- name: Install ncc
run: npm i -g @vercel/ncc
- name: Install conntrack
run: sudo apt-get install -y conntrack
- name: Build
run: ncc build src/run.ts -o lib
- uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
name: Install Kubectl
- id: setup-minikube
name: Setup Minikube
uses: medyagh/setup-minikube@e3c7f79eb1e997eabccc536a6cf318a2b0fe19d9 # v0.0.20
with:
minikube-version: 1.34.0
kubernetes-version: 1.31.0
driver: 'none'
timeout-minutes: 3
- name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }}
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
name: Install Python
with:
python-version: '3.x'
- name: Cleaning any previously created items
run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
@@ -12,21 +12,45 @@ on:
jobs:
run-integration-test:
name: Blue-Green Ingress Tests
name: Run Minikube Integration Tests
runs-on: ubuntu-22.04
env:
KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup
name: Setup Minikube Environment
timeout-minutes: 5
- name: Install dependencies
run: |
rm -rf node_modules/
npm install
- name: Install ncc
run: npm i -g @vercel/ncc
- name: Install conntrack
run: sudo apt-get install -y conntrack
- name: Build
run: ncc build src/run.ts -o lib
- uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
name: Install Kubectl
- id: setup-minikube
name: Setup Minikube
uses: medyagh/setup-minikube@e3c7f79eb1e997eabccc536a6cf318a2b0fe19d9 # v0.0.20
with:
minikube-version: 1.34.0
kubernetes-version: 1.31.0
driver: 'none'
timeout-minutes: 3
- name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }}
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
name: Install Python
with:
python-version: '3.x'
- name: Cleaning any previously created items
run: |
python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service' ${{ env.NAMESPACE }}
@@ -12,21 +12,45 @@ on:
jobs:
run-integration-test:
name: Blue-Green Service Tests
name: Run Minikube Integration Tests
runs-on: ubuntu-22.04
env:
KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup
name: Setup Minikube Environment
timeout-minutes: 5
- name: Install dependencies
run: |
rm -rf node_modules/
npm install
- name: Install ncc
run: npm i -g @vercel/ncc
- name: Install conntrack
run: sudo apt-get install -y conntrack
- name: Build
run: ncc build src/run.ts -o lib
- uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
name: Install Kubectl
- id: setup-minikube
name: Setup Minikube
uses: medyagh/setup-minikube@e3c7f79eb1e997eabccc536a6cf318a2b0fe19d9 # v0.0.20
with:
minikube-version: 1.34.0
kubernetes-version: 1.31.0
driver: 'none'
timeout-minutes: 3
- name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }}
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
name: Install Python
with:
python-version: '3.x'
- name: Cleaning any previously created items
run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
@@ -12,23 +12,56 @@ on:
jobs:
run-integration-test:
name: Blue-Green SMI Tests
name: Run Minikube Integration Tests
runs-on: ubuntu-22.04
env:
KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup
name: Setup Minikube Environment
timeout-minutes: 5
- name: Install dependencies
run: |
rm -rf node_modules/
npm install
- name: Install ncc
run: npm i -g @vercel/ncc
- name: Install conntrack
run: sudo apt-get install -y conntrack
- name: Build
run: ncc build src/run.ts -o lib
- uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
name: Install Kubectl
- id: setup-minikube
name: Setup Minikube
uses: medyagh/setup-minikube@e3c7f79eb1e997eabccc536a6cf318a2b0fe19d9 # v0.0.20
with:
install-smi: 'true'
minikube-version: 1.34.0
kubernetes-version: 1.31.0
driver: 'none'
timeout-minutes: 3
- name: Install linkerd and add controlplane to cluster
run: |
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install-edge | sh
export PATH=$PATH:/home/runner/.linkerd2/bin
curl -sL https://linkerd.github.io/linkerd-smi/install | sh
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
linkerd install --crds | kubectl apply -f -
linkerd install --set proxyInit.runAsRoot=true | kubectl apply -f -
linkerd smi install | kubectl apply -f -
- name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }}
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
name: Install Python
with:
python-version: '3.x'
- name: Cleaning any previously created items
run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
@@ -12,21 +12,45 @@ on:
jobs:
run-integration-test:
name: Canary Pod Tests
name: Run Minikube Integration Tests
runs-on: ubuntu-22.04
env:
KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup
name: Setup Minikube Environment
timeout-minutes: 5
- name: Install dependencies
run: |
rm -rf node_modules/
npm install
- name: Install ncc
run: npm i -g @vercel/ncc
- name: Install conntrack
run: sudo apt-get install -y conntrack
- name: Build
run: ncc build src/run.ts -o lib
- uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
name: Install Kubectl
- id: setup-minikube
name: Setup Minikube
uses: medyagh/setup-minikube@e3c7f79eb1e997eabccc536a6cf318a2b0fe19d9 # v0.0.20
with:
minikube-version: 1.34.0
kubernetes-version: 1.31.0
driver: 'none'
timeout-minutes: 3
- name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }}
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
name: Install Python
with:
python-version: '3.x'
- name: Cleaning any previously created items
run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
@@ -12,23 +12,56 @@ on:
jobs:
run-integration-test:
name: Canary SMI Tests
name: Run Minikube Integration Tests
runs-on: ubuntu-22.04
env:
KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup
name: Setup Minikube Environment
timeout-minutes: 5
- name: Install dependencies
run: |
rm -rf node_modules/
npm install
- name: Install ncc
run: npm i -g @vercel/ncc
- name: Install conntrack
run: sudo apt-get install -y conntrack
- name: Build
run: ncc build src/run.ts -o lib
- uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
name: Install Kubectl
- id: setup-minikube
name: Setup Minikube
uses: medyagh/setup-minikube@e3c7f79eb1e997eabccc536a6cf318a2b0fe19d9 # v0.0.20
with:
install-smi: 'true'
minikube-version: 1.34.0
kubernetes-version: 1.31.0
driver: 'none'
timeout-minutes: 3
- name: Install Linkerd and SMI
run: |
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install-edge | sh
export PATH=$PATH:/home/runner/.linkerd2/bin
curl -sL https://linkerd.github.io/linkerd-smi/install | sh
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
linkerd install --crds | kubectl apply -f -
linkerd install --set proxyInit.runAsRoot=true | kubectl apply -f -
linkerd smi install | kubectl apply -f -
- name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }}
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
name: Install Python
with:
python-version: '3.x'
- name: Cleaning any previously created items
run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
@@ -14,18 +14,37 @@ on:
jobs:
run-integration-test:
name: Namespace Optional Tests
name: Run Namespace Optional Integration Tests
runs-on: ubuntu-22.04
env:
KUBECONFIG: /home/runner/.kube/config
NAMESPACE1: integration-test-namespace1-${{ github.run_id }}
NAMESPACE2: integration-test-namespace2-${{ github.run_id }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup
name: Setup Minikube Environment
timeout-minutes: 5
- name: Install dependencies
run: |
rm -rf node_modules/
npm install
- name: Install ncc
run: npm i -g @vercel/ncc
- name: Install conntrack
run: sudo apt-get install -y conntrack
- name: Build
run: ncc build src/run.ts -o lib
- uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
name: Install Kubectl
- id: setup-minikube
name: Setup Minikube
uses: medyagh/setup-minikube@e3c7f79eb1e997eabccc536a6cf318a2b0fe19d9 # v0.0.20
with:
minikube-version: 1.34.0
kubernetes-version: 1.31.0
driver: 'none'
timeout-minutes: 3
- name: Create namespaces for tests
run: |
@@ -33,6 +52,11 @@ jobs:
kubectl create ns ${{ env.NAMESPACE2 }}
kubectl create ns test-namespace
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
name: Install Python
with:
python-version: '3.x'
- name: Cleaning any previously created items
run: |
python test/integration/k8s-deploy-delete.py 'Deployment' 'all' ${{ env.NAMESPACE1 }}
@@ -19,24 +19,24 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install dependencies
run: |
rm -rf node_modules/
npm install
- name: Install ncc
run: npm i -g @vercel/ncc
- name: Build
run: npm run build
run: ncc build src/run.ts -o lib
- name: Azure login
uses: azure/login@v3.0.0
uses: azure/login@v2.3.0
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- uses: Azure/setup-kubectl@15650b3ad78fff148532a140b8a4c821796b2d7b # v5.0.0
- uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
name: Install Kubectl
- name: Create private AKS cluster and set context
@@ -51,7 +51,7 @@ jobs:
run: |
az aks command invoke --resource-group ${{ env.NAMESPACE }} --name ${{ env.NAMESPACE }} --command "kubectl create ns ${{ env.NAMESPACE }}"
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 6.2.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
name: Install Python
with:
python-version: '3.x'
@@ -12,21 +12,45 @@ on:
jobs:
run-integration-test:
name: Resource Annotation Tests
name: Run Minikube Integration Tests
runs-on: ubuntu-22.04
env:
KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup
name: Setup Minikube Environment
timeout-minutes: 5
- name: Install dependencies
run: |
rm -rf node_modules/
npm install
- name: Install ncc
run: npm i -g @vercel/ncc
- name: Install conntrack
run: sudo apt-get install -y conntrack
- name: Build
run: ncc build src/run.ts -o lib
- uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
name: Install Kubectl
- id: setup-minikube
name: Setup Minikube
uses: medyagh/setup-minikube@e3c7f79eb1e997eabccc536a6cf318a2b0fe19d9 # v0.0.20
with:
minikube-version: 1.34.0
kubernetes-version: 1.31.0
driver: 'none'
timeout-minutes: 3
- name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }}
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
name: Install Python
with:
python-version: '3.x'
- name: Cleaning any previously created items
run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
name: Run Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: |
npm install
npm test
-6
View File
@@ -1,9 +1,3 @@
npm run typecheck || {
echo ""
echo "❌ Type check failed."
echo "💡 Run 'npm run typecheck' to see errors."
exit 1
}
npm test
npm run format-check || {
echo ""
-26
View File
@@ -1,31 +1,5 @@
# Changelog
## [6.0.0] - 2026-04-17
### Changed
- #504 [Update Node.js runtime from node20 to node24](https://github.com/Azure/k8s-deploy/pull/504)
- #500 [Update action version references in README to latest majors](https://github.com/Azure/k8s-deploy/pull/500)
### Security
- #506 [Bump undici from 6.23.0 to 6.24.1](https://github.com/Azure/k8s-deploy/pull/506)
- #513 [Bump vite from 8.0.3 to 8.0.5](https://github.com/Azure/k8s-deploy/pull/513)
- #509 [Bump the actions group across 1 directory with 4 updates](https://github.com/Azure/k8s-deploy/pull/509)
- #510 [Bump the actions group across 1 directory with 2 updates](https://github.com/Azure/k8s-deploy/pull/510)
- #511 [Bump vitest from 4.1.1 to 4.1.2](https://github.com/Azure/k8s-deploy/pull/511)
- #514 [Bump the actions group with 2 updates](https://github.com/Azure/k8s-deploy/pull/514)
- #501 [Bump @types/node from 25.3.3 to 25.4.0](https://github.com/Azure/k8s-deploy/pull/501)
## [5.1.0] - 2026-03-03
### Added
- #458 [Ensure error messages display the correct namespace](https://github.com/Azure/k8s-deploy/pull/458)
- #482 [docker driver](https://github.com/Azure/k8s-deploy/pull/482)
- #492 [Migrate to esbuild/Vitest and upgrade @actions/\* to ESM-only versions](https://github.com/Azure/k8s-deploy/pull/492)
- #498 [Add typecheck to build script](https://github.com/Azure/k8s-deploy/pull/498)
## [5.0.4] - 2025-08-05
### Added
+13 -13
View File
@@ -1,6 +1,6 @@
# Deploy manifests action for Kubernetes
This action is used to deploy manifests to Kubernetes clusters. It requires that the cluster context be set earlier in the workflow by using either the [Azure/aks-set-context](https://github.com/Azure/aks-set-context/tree/releases/v4) action or the [Azure/k8s-set-context](https://github.com/Azure/k8s-set-context/tree/releases/v4) action. It also requires Kubectl to be installed (you can use the [Azure/setup-kubectl](https://github.com/Azure/setup-kubectl) action).
This action is used to deploy manifests to Kubernetes clusters. It requires that the cluster context be set earlier in the workflow by using either the [Azure/aks-set-context](https://github.com/Azure/aks-set-context/tree/releases/v1) action or the [Azure/k8s-set-context](https://github.com/Azure/k8s-set-context/tree/releases/v1) action. It also requires Kubectl to be installed (you can use the [Azure/setup-kubectl](https://github.com/Azure/setup-kubectl) action).
If you are looking to automate your workflows to deploy to [Azure Web Apps](https://azure.microsoft.com/en-us/services/app-service/web/) and [Azure Web App for Containers](https://azure.microsoft.com/en-us/services/app-service/containers/), consider using [`Azure/webapps-deploy`](https://github.com/Azure/webapps-deploy) action.
@@ -310,9 +310,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: Azure/docker-login@v2
- uses: Azure/docker-login@v1
with:
login-server: contoso.azurecr.io
username: ${{ secrets.REGISTRY_USERNAME }}
@@ -331,7 +331,7 @@ jobs:
cluster-name: contoso
resource-group: contoso-rg
- uses: Azure/k8s-create-secret@v5
- uses: Azure/k8s-create-secret@v4
with:
container-registry-url: contoso.azurecr.io
container-registry-username: ${{ secrets.REGISTRY_USERNAME }}
@@ -359,9 +359,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: Azure/docker-login@v2
- uses: Azure/docker-login@v1
with:
login-server: contoso.azurecr.io
username: ${{ secrets.REGISTRY_USERNAME }}
@@ -377,14 +377,14 @@ jobs:
with:
kubeconfig: ${{ secrets.KUBE_CONFIG }}
- uses: Azure/k8s-create-secret@v5
- uses: Azure/k8s-create-secret@v4
with:
container-registry-url: contoso.azurecr.io
container-registry-username: ${{ secrets.REGISTRY_USERNAME }}
container-registry-password: ${{ secrets.REGISTRY_PASSWORD }}
secret-name: demo-k8s-secret
- uses: Azure/k8s-deploy@v5
- uses: Azure/k8s-deploy@v4
with:
action: deploy
manifests: |
@@ -409,9 +409,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: Azure/docker-login@v2
- uses: Azure/docker-login@v1
with:
login-server: contoso.azurecr.io
username: ${{ secrets.REGISTRY_USERNAME }}
@@ -433,9 +433,9 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@master
- uses: Azure/docker-login@v2
- uses: Azure/docker-login@v1
with:
login-server: contoso.azurecr.io
username: ${{ secrets.REGISTRY_USERNAME }}
@@ -450,7 +450,7 @@ jobs:
cluster-name: contoso
resource-group: contoso-rg
- uses: Azure/k8s-create-secret@v5
- uses: Azure/k8s-create-secret@v4
with:
namespace: ${{ env.NAMESPACE }}
container-registry-url: contoso.azurecr.io
+1 -1
View File
@@ -96,5 +96,5 @@ inputs:
branding:
color: 'green'
runs:
using: 'node24'
using: 'node20'
main: 'lib/index.js'
+20
View File
@@ -0,0 +1,20 @@
module.exports = {
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
transform: {
'\\.[jt]sx?$': 'babel-jest'
},
transformIgnorePatterns: [
'node_modules/(?!' +
[
'@octokit',
'universal-user-agent',
'before-after-hook',
'minimist'
].join('|') +
')'
],
verbose: true,
testTimeout: 9000
}
+15805 -28058
View File
File diff suppressed because one or more lines are too long
+9605 -1569
View File
File diff suppressed because it is too large Load Diff
+23 -19
View File
@@ -1,36 +1,40 @@
{
"name": "k8s-deploy-action",
"version": "6.0.0",
"version": "5.0.0",
"author": "Deepak Sattiraju",
"license": "MIT",
"type": "module",
"scripts": {
"build": "tsc --noEmit && esbuild src/run.ts --bundle --platform=node --target=node20 --format=esm --outfile=lib/index.js --banner:js=\"import { createRequire } from 'module';const require = createRequire(import.meta.url);\"",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"coverage": "vitest run --coverage",
"prebuild": "npm i @vercel/ncc",
"build": "ncc build src/run.ts -o lib",
"test": "jest",
"coverage": "jest --coverage=true",
"format": "prettier --write .",
"format-check": "prettier --check .",
"prepare": "husky"
},
"dependencies": {
"@actions/core": "^3.0.0",
"@actions/exec": "^3.0.0",
"@actions/io": "^3.0.2",
"@actions/tool-cache": "4.0.0",
"@octokit/core": "^7.0.6",
"@octokit/plugin-retry": "^8.1.0",
"js-yaml": "4.1.1",
"@actions/core": "^1.11.1",
"@actions/exec": "^1.0.0",
"@actions/io": "^1.1.3",
"@actions/tool-cache": "2.0.2",
"@babel/preset-env": "^7.28.0",
"@babel/preset-typescript": "^7.27.1",
"@octokit/core": "^7.0.3",
"@octokit/plugin-retry": "^8.0.1",
"@types/minipass": "^3.3.5",
"husky": "^9.1.7",
"js-yaml": "4.1.0",
"minimist": "^1.2.8"
},
"devDependencies": {
"@types/jest": "^30.0.0",
"@types/js-yaml": "^4.0.9",
"@types/minimist": "^1.2.5",
"@types/node": "^25.5.2",
"esbuild": "^0.28",
"husky": "^9.1.7",
"prettier": "^3.8.1",
"typescript": "6.0.2",
"vitest": "^4"
"@types/node": "^24.2.0",
"@vercel/ncc": "^0.38.3",
"jest": "^30.0.5",
"prettier": "^3.6.2",
"ts-jest": "^29.4.1",
"typescript": "5.9.2"
}
}
+8 -8
View File
@@ -1,19 +1,19 @@
import * as core from '@actions/core'
import * as models from '../types/kubernetesTypes.js'
import * as KubernetesConstants from '../types/kubernetesTypes.js'
import {Kubectl, Resource} from '../types/kubectl.js'
import * as models from '../types/kubernetesTypes'
import * as KubernetesConstants from '../types/kubernetesTypes'
import {Kubectl, Resource} from '../types/kubectl'
import {
getResources,
updateManifestFiles
} from '../utilities/manifestUpdateUtils.js'
} from '../utilities/manifestUpdateUtils'
import {
annotateAndLabelResources,
checkManifestStability,
deployManifests
} from '../strategyHelpers/deploymentHelper.js'
import {DeploymentStrategy} from '../types/deploymentStrategy.js'
import {parseTrafficSplitMethod} from '../types/trafficSplitMethod.js'
import {ClusterType} from '../inputUtils.js'
} from '../strategyHelpers/deploymentHelper'
import {DeploymentStrategy} from '../types/deploymentStrategy'
import {parseTrafficSplitMethod} from '../types/trafficSplitMethod'
import {ClusterType} from '../inputUtils'
export const ResourceTypeManagedCluster =
'Microsoft.ContainerService/managedClusters'
export const ResourceTypeFleet = 'Microsoft.ContainerService/fleets'
+18 -18
View File
@@ -1,44 +1,44 @@
import * as core from '@actions/core'
import * as canaryDeploymentHelper from '../strategyHelpers/canary/canaryHelper.js'
import * as SMICanaryDeploymentHelper from '../strategyHelpers/canary/smiCanaryHelper.js'
import * as PodCanaryHelper from '../strategyHelpers/canary/podCanaryHelper.js'
import * as canaryDeploymentHelper from '../strategyHelpers/canary/canaryHelper'
import * as SMICanaryDeploymentHelper from '../strategyHelpers/canary/smiCanaryHelper'
import * as PodCanaryHelper from '../strategyHelpers/canary/podCanaryHelper'
import {
getResources,
updateManifestFiles
} from '../utilities/manifestUpdateUtils.js'
import {annotateAndLabelResources} from '../strategyHelpers/deploymentHelper.js'
import * as models from '../types/kubernetesTypes.js'
import * as KubernetesManifestUtility from '../utilities/manifestStabilityUtils.js'
} from '../utilities/manifestUpdateUtils'
import {annotateAndLabelResources} from '../strategyHelpers/deploymentHelper'
import * as models from '../types/kubernetesTypes'
import * as KubernetesManifestUtility from '../utilities/manifestStabilityUtils'
import {
deleteGreenObjects,
getManifestObjects,
NONE_LABEL_VALUE
} from '../strategyHelpers/blueGreen/blueGreenHelper.js'
} from '../strategyHelpers/blueGreen/blueGreenHelper'
import {BlueGreenManifests} from '../types/blueGreenTypes.js'
import {DeployResult} from '../types/deployResult.js'
import {BlueGreenManifests} from '../types/blueGreenTypes'
import {DeployResult} from '../types/deployResult'
import {
promoteBlueGreenIngress,
promoteBlueGreenService,
promoteBlueGreenSMI
} from '../strategyHelpers/blueGreen/promote.js'
} from '../strategyHelpers/blueGreen/promote'
import {
routeBlueGreenService,
routeBlueGreenIngressUnchanged,
routeBlueGreenSMI
} from '../strategyHelpers/blueGreen/route.js'
} from '../strategyHelpers/blueGreen/route'
import {cleanupSMI} from '../strategyHelpers/blueGreen/smiBlueGreenHelper.js'
import {Kubectl, Resource} from '../types/kubectl.js'
import {DeploymentStrategy} from '../types/deploymentStrategy.js'
import {cleanupSMI} from '../strategyHelpers/blueGreen/smiBlueGreenHelper'
import {Kubectl, Resource} from '../types/kubectl'
import {DeploymentStrategy} from '../types/deploymentStrategy'
import {
parseTrafficSplitMethod,
TrafficSplitMethod
} from '../types/trafficSplitMethod.js'
import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy.js'
import {ClusterType} from '../inputUtils.js'
} from '../types/trafficSplitMethod'
import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy'
import {ClusterType} from '../inputUtils'
export async function promote(
kubectl: Kubectl,
+9 -9
View File
@@ -1,20 +1,20 @@
import * as core from '@actions/core'
import * as canaryDeploymentHelper from '../strategyHelpers/canary/canaryHelper.js'
import * as SMICanaryDeploymentHelper from '../strategyHelpers/canary/smiCanaryHelper.js'
import {Kubectl} from '../types/kubectl.js'
import {BlueGreenManifests} from '../types/blueGreenTypes.js'
import * as canaryDeploymentHelper from '../strategyHelpers/canary/canaryHelper'
import * as SMICanaryDeploymentHelper from '../strategyHelpers/canary/smiCanaryHelper'
import {Kubectl} from '../types/kubectl'
import {BlueGreenManifests} from '../types/blueGreenTypes'
import {
rejectBlueGreenIngress,
rejectBlueGreenService,
rejectBlueGreenSMI
} from '../strategyHelpers/blueGreen/reject.js'
import {getManifestObjects} from '../strategyHelpers/blueGreen/blueGreenHelper.js'
import {DeploymentStrategy} from '../types/deploymentStrategy.js'
} from '../strategyHelpers/blueGreen/reject'
import {getManifestObjects} from '../strategyHelpers/blueGreen/blueGreenHelper'
import {DeploymentStrategy} from '../types/deploymentStrategy'
import {
parseTrafficSplitMethod,
TrafficSplitMethod
} from '../types/trafficSplitMethod.js'
import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy.js'
} from '../types/trafficSplitMethod'
import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy'
export async function reject(
kubectl: Kubectl,
+2 -5
View File
@@ -1,8 +1,5 @@
import {parseResourceTypeInput} from './inputUtils.js'
import {
ResourceTypeFleet,
ResourceTypeManagedCluster
} from './actions/deploy.js'
import {parseResourceTypeInput} from './inputUtils'
import {ResourceTypeFleet, ResourceTypeManagedCluster} from './actions/deploy'
describe('InputUtils', () => {
describe('parseResourceTypeInput', () => {
+2 -5
View File
@@ -1,9 +1,6 @@
import * as core from '@actions/core'
import {parseAnnotations} from './types/annotations.js'
import {
ResourceTypeFleet,
ResourceTypeManagedCluster
} from './actions/deploy.js'
import {parseAnnotations} from './types/annotations'
import {ResourceTypeFleet, ResourceTypeManagedCluster} from './actions/deploy'
export const inputAnnotations = parseAnnotations(
core.getInput('annotations', {required: false})
+11 -11
View File
@@ -1,19 +1,19 @@
import * as core from '@actions/core'
import {getKubectlPath, Kubectl} from './types/kubectl.js'
import {getKubectlPath, Kubectl} from './types/kubectl'
import {
deploy,
ResourceTypeFleet,
ResourceTypeManagedCluster
} from './actions/deploy.js'
import {ClusterType} from './inputUtils.js'
import {promote} from './actions/promote.js'
import {reject} from './actions/reject.js'
import {Action, parseAction} from './types/action.js'
import {parseDeploymentStrategy} from './types/deploymentStrategy.js'
import {getFilesFromDirectoriesAndURLs} from './utilities/fileUtils.js'
import {PrivateKubectl} from './types/privatekubectl.js'
import {parseResourceTypeInput} from './inputUtils.js'
import {parseDuration} from './utilities/durationUtils.js'
} from './actions/deploy'
import {ClusterType} from './inputUtils'
import {promote} from './actions/promote'
import {reject} from './actions/reject'
import {Action, parseAction} from './types/action'
import {parseDeploymentStrategy} from './types/deploymentStrategy'
import {getFilesFromDirectoriesAndURLs} from './utilities/fileUtils'
import {PrivateKubectl} from './types/privatekubectl'
import {parseResourceTypeInput} from './inputUtils'
import {parseDuration} from './utilities/durationUtils'
export async function run() {
// verify kubeconfig is set
@@ -1,5 +1,3 @@
import {vi} from 'vitest'
import type {MockInstance} from 'vitest'
import {
deployWithLabel,
deleteGreenObjects,
@@ -10,16 +8,16 @@ import {
getNewBlueGreenObject,
GREEN_LABEL_VALUE,
isServiceRouted
} from './blueGreenHelper.js'
import {BlueGreenDeployment} from '../../types/blueGreenTypes.js'
import * as bgHelper from './blueGreenHelper.js'
import {Kubectl} from '../../types/kubectl.js'
import * as fileHelper from '../../utilities/fileUtils.js'
import {K8sObject} from '../../types/k8sObject.js'
import * as manifestUpdateUtils from '../../utilities/manifestUpdateUtils.js'
} from './blueGreenHelper'
import {BlueGreenDeployment} from '../../types/blueGreenTypes'
import * as bgHelper from './blueGreenHelper'
import {Kubectl} from '../../types/kubectl'
import * as fileHelper from '../../utilities/fileUtils'
import {K8sObject} from '../../types/k8sObject'
import * as manifestUpdateUtils from '../../utilities/manifestUpdateUtils'
import {ExecOutput} from '@actions/exec'
vi.mock('../../types/kubectl')
jest.mock('../../types/kubectl')
const kubectl = new Kubectl('')
const TEST_TIMEOUT = '60s'
@@ -39,17 +37,17 @@ const MOCK_EXEC_OUTPUT = {
describe('bluegreenhelper functions', () => {
let testObjects
beforeEach(() => {
vi.restoreAllMocks()
vi.mocked(Kubectl).mockClear()
//@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(['test/unit/manifests/test-ingress.yml'])
vi.spyOn(fileHelper, 'writeObjectsToFile').mockImplementationOnce(() => [
''
])
jest
.spyOn(fileHelper, 'writeObjectsToFile')
.mockImplementationOnce(() => [''])
})
test('correctly deletes services and workloads according to label', async () => {
vi.spyOn(bgHelper, 'deleteObjects').mockReturnValue({} as Promise<void>)
jest.spyOn(bgHelper, 'deleteObjects').mockReturnValue({} as Promise<void>)
const value = await deleteGreenObjects(
kubectl,
@@ -67,16 +65,21 @@ describe('bluegreenhelper functions', () => {
})
test('handles timeout when deleting objects', async () => {
const deleteMock = vi.fn().mockResolvedValue(MOCK_EXEC_OUTPUT)
kubectl.delete = deleteMock
// Mock deleteObjects to prevent actual execution
const deleteSpy = jest
.spyOn(kubectl, 'delete')
.mockResolvedValue(MOCK_EXEC_OUTPUT)
const deleteList = EXPECTED_GREEN_OBJECTS
await bgHelper.deleteObjects(
kubectl,
EXPECTED_GREEN_OBJECTS,
TEST_TIMEOUT
)
await bgHelper.deleteObjects(kubectl, deleteList, TEST_TIMEOUT)
expect(deleteMock).toHaveBeenCalledTimes(deleteList.length)
deleteList.forEach(({name, kind}) => {
expect(deleteMock).toHaveBeenCalledWith(
// Verify kubectl.delete is called with timeout for each object in deleteList
expect(deleteSpy).toHaveBeenCalledTimes(EXPECTED_GREEN_OBJECTS.length)
EXPECTED_GREEN_OBJECTS.forEach(({name, kind}) => {
expect(deleteSpy).toHaveBeenCalledWith(
[kind, name],
undefined,
TEST_TIMEOUT
@@ -132,7 +135,7 @@ describe('bluegreenhelper functions', () => {
})
test('correctly makes labeled workloads', async () => {
const kubectlApplySpy = vi.spyOn(kubectl, 'apply').mockResolvedValue({
const kubectlApplySpy = jest.spyOn(kubectl, 'apply').mockResolvedValue({
stdout: 'deployment.apps/nginx-deployment created',
stderr: '',
exitCode: 0
@@ -176,9 +179,9 @@ describe('bluegreenhelper functions', () => {
exitCode: 0
}
vi.spyOn(kubectl, 'getResource').mockImplementation(() =>
Promise.resolve(mockExecOutput)
)
jest
.spyOn(kubectl, 'getResource')
.mockImplementation(() => Promise.resolve(mockExecOutput))
const fetched = await fetchResource(
kubectl,
'nginx-deployment',
@@ -206,12 +209,12 @@ describe('bluegreenhelper functions', () => {
]
for (const testCase of errorTestCases) {
const spy = vi.spyOn(kubectl, 'getResource')
const spy = jest.spyOn(kubectl, 'getResource')
if (testCase.mockOutput) {
spy.mockImplementation(() => Promise.resolve(testCase.mockOutput))
} else {
spy.mockResolvedValue(null)
spy.mockImplementation()
}
const fetched = await fetchResource(
@@ -232,11 +235,10 @@ describe('bluegreenhelper functions', () => {
stderr: ''
} as ExecOutput
vi.spyOn(kubectl, 'getResource').mockResolvedValue(mockExecOutput)
vi.spyOn(
manifestUpdateUtils,
'UnsetClusterSpecificDetails'
).mockImplementation(() => {
jest.spyOn(kubectl, 'getResource').mockResolvedValue(mockExecOutput)
jest
.spyOn(manifestUpdateUtils, 'UnsetClusterSpecificDetails')
.mockImplementation(() => {
throw new Error('test error')
})
@@ -265,7 +267,7 @@ describe('bluegreenhelper functions', () => {
describe('deployObjects', () => {
let mockObjects: any[]
let kubectlApplySpy: MockInstance
let kubectlApplySpy: jest.SpyInstance
const mockSuccessResult: ExecOutput = {
stdout: 'deployment.apps/nginx-deployment created',
@@ -283,11 +285,11 @@ describe('bluegreenhelper functions', () => {
// //@ts-ignore
// Kubectl.mockClear()
mockObjects = [testObjects.deploymentEntityList[0]]
kubectlApplySpy = vi.spyOn(kubectl, 'apply')
kubectlApplySpy = jest.spyOn(kubectl, 'apply')
})
afterEach(() => {
vi.clearAllMocks()
jest.clearAllMocks()
})
it('should return execution result and manifest files when kubectl apply succeeds', async () => {
@@ -2,27 +2,27 @@ import * as core from '@actions/core'
import * as fs from 'fs'
import * as yaml from 'js-yaml'
import {DeployResult} from '../../types/deployResult.js'
import {K8sObject, K8sDeleteObject} from '../../types/k8sObject.js'
import {Kubectl} from '../../types/kubectl.js'
import {DeployResult} from '../../types/deployResult'
import {K8sObject, K8sDeleteObject} from '../../types/k8sObject'
import {Kubectl} from '../../types/kubectl'
import {
isDeploymentEntity,
isIngressEntity,
isServiceEntity,
KubernetesWorkload
} from '../../types/kubernetesTypes.js'
} from '../../types/kubernetesTypes'
import {
BlueGreenDeployment,
BlueGreenManifests
} from '../../types/blueGreenTypes.js'
import * as fileHelper from '../../utilities/fileUtils.js'
import {updateSpecLabels} from '../../utilities/manifestSpecLabelUtils.js'
import {checkForErrors} from '../../utilities/kubectlUtils.js'
} from '../../types/blueGreenTypes'
import * as fileHelper from '../../utilities/fileUtils'
import {updateSpecLabels} from '../../utilities/manifestSpecLabelUtils'
import {checkForErrors} from '../../utilities/kubectlUtils'
import {
UnsetClusterSpecificDetails,
updateObjectLabels,
updateSelectorLabels
} from '../../utilities/manifestUpdateUtils.js'
} from '../../utilities/manifestUpdateUtils'
export const GREEN_LABEL_VALUE = 'green'
export const NONE_LABEL_VALUE = 'None'
+52 -52
View File
@@ -1,23 +1,21 @@
import {vi} from 'vitest'
import type {MockInstance} from 'vitest'
import {BlueGreenDeployment} from '../../types/blueGreenTypes.js'
import {BlueGreenDeployment} from '../../types/blueGreenTypes'
import {
deployBlueGreen,
deployBlueGreenIngress,
deployBlueGreenService,
deployBlueGreenSMI
} from './deploy.js'
import * as routeTester from './route.js'
import {Kubectl} from '../../types/kubectl.js'
import {RouteStrategy} from '../../types/routeStrategy.js'
import * as TSutils from '../../utilities/trafficSplitUtils.js'
import * as bgHelper from './blueGreenHelper.js'
import * as smiHelper from './smiBlueGreenHelper.js'
} from './deploy'
import * as routeTester from './route'
import {Kubectl} from '../../types/kubectl'
import {RouteStrategy} from '../../types/routeStrategy'
import * as TSutils from '../../utilities/trafficSplitUtils'
import * as bgHelper from './blueGreenHelper'
import * as smiHelper from './smiBlueGreenHelper'
import {ExecOutput} from '@actions/exec'
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
vi.mock('../../types/kubectl')
jest.mock('../../types/kubectl')
// Shared variables and mock objects used across all test suites
const mockDeployResult = {
@@ -32,7 +30,7 @@ const mockBgDeployment: BlueGreenDeployment = {
describe('deploy tests', () => {
let kubectl: Kubectl
let kubectlApplySpy: MockInstance
let kubectlApplySpy: jest.SpyInstance
const mockSuccessResult: ExecOutput = {
stdout: 'deployment.apps/nginx-deployment created',
@@ -47,20 +45,21 @@ describe('deploy tests', () => {
}
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
//@ts-ignore
Kubectl.mockClear()
kubectl = new Kubectl('')
kubectlApplySpy = vi.spyOn(kubectl, 'apply')
kubectlApplySpy = jest.spyOn(kubectl, 'apply')
})
test('correctly determines deploy type and acts accordingly', async () => {
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
vi.spyOn(routeTester, 'routeBlueGreenForDeploy').mockImplementation(() =>
Promise.resolve(mockBgDeployment)
)
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(() =>
Promise.resolve('v1alpha3')
)
jest
.spyOn(routeTester, 'routeBlueGreenForDeploy')
.mockImplementation(() => Promise.resolve(mockBgDeployment))
jest
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
.mockImplementation(() => Promise.resolve('v1alpha3'))
const ingressResult = await deployBlueGreen(
kubectl,
@@ -113,12 +112,12 @@ describe('deploy tests', () => {
fn: () =>
deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.INGRESS),
setup: () => {
vi.spyOn(routeTester, 'routeBlueGreenForDeploy').mockImplementation(
() => Promise.resolve(mockBgDeployment)
)
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(
() => Promise.resolve('v1alpha3')
)
jest
.spyOn(routeTester, 'routeBlueGreenForDeploy')
.mockImplementation(() => Promise.resolve(mockBgDeployment))
jest
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
.mockImplementation(() => Promise.resolve('v1alpha3'))
}
},
{
@@ -126,24 +125,24 @@ describe('deploy tests', () => {
fn: () =>
deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.SERVICE),
setup: () => {
vi.spyOn(routeTester, 'routeBlueGreenForDeploy').mockImplementation(
() => Promise.resolve(mockBgDeployment)
)
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(
() => Promise.resolve('v1alpha3')
)
jest
.spyOn(routeTester, 'routeBlueGreenForDeploy')
.mockImplementation(() => Promise.resolve(mockBgDeployment))
jest
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
.mockImplementation(() => Promise.resolve('v1alpha3'))
}
},
{
name: 'should throw error when kubectl apply fails during blue/green deployment with SMI strategy',
fn: () => deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.SMI),
setup: () => {
vi.spyOn(routeTester, 'routeBlueGreenForDeploy').mockImplementation(
() => Promise.resolve(mockBgDeployment)
)
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(
() => Promise.resolve('v1alpha3')
)
jest
.spyOn(routeTester, 'routeBlueGreenForDeploy')
.mockImplementation(() => Promise.resolve(mockBgDeployment))
jest
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
.mockImplementation(() => Promise.resolve('v1alpha3'))
}
},
{
@@ -182,7 +181,8 @@ describe('deploy timeout tests', () => {
let kubectl: Kubectl
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
//@ts-ignore
Kubectl.mockClear()
kubectl = new Kubectl('')
})
@@ -190,16 +190,16 @@ describe('deploy timeout tests', () => {
const timeout = '300s'
// Mock the helper functions that are actually called
const deployWithLabelSpy = vi
const deployWithLabelSpy = jest
.spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment)
const deployObjectsSpy = vi
const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects')
.mockResolvedValue(mockDeployResult)
const setupSMISpy = vi
const setupSMISpy = jest
.spyOn(smiHelper, 'setupSMI')
.mockResolvedValue(mockBgDeployment)
const routeSpy = vi
const routeSpy = jest
.spyOn(routeTester, 'routeBlueGreenForDeploy')
.mockResolvedValue(mockBgDeployment)
@@ -258,10 +258,10 @@ describe('deploy timeout tests', () => {
const timeout = '240s'
// Mock the dependencies
const deployWithLabelSpy = vi
const deployWithLabelSpy = jest
.spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment)
const deployObjectsSpy = vi
const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects')
.mockResolvedValue(mockDeployResult)
@@ -290,10 +290,10 @@ describe('deploy timeout tests', () => {
const timeout = '180s'
// Mock the dependencies
const deployWithLabelSpy = vi
const deployWithLabelSpy = jest
.spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment)
const deployObjectsSpy = vi
const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects')
.mockResolvedValue(mockDeployResult)
@@ -322,13 +322,13 @@ describe('deploy timeout tests', () => {
const timeout = '360s'
// Mock the dependencies
const setupSMISpy = vi
const setupSMISpy = jest
.spyOn(smiHelper, 'setupSMI')
.mockResolvedValue(mockBgDeployment)
const deployObjectsSpy = vi
const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects')
.mockResolvedValue(mockDeployResult)
const deployWithLabelSpy = vi
const deployWithLabelSpy = jest
.spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment)
@@ -354,10 +354,10 @@ describe('deploy timeout tests', () => {
})
test('deploy functions without timeout should pass undefined', async () => {
const deployWithLabelSpy = vi
const deployWithLabelSpy = jest
.spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment)
const deployObjectsSpy = vi
const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects')
.mockResolvedValue(mockDeployResult)
+7 -7
View File
@@ -1,23 +1,23 @@
import * as core from '@actions/core'
import {Kubectl} from '../../types/kubectl.js'
import {Kubectl} from '../../types/kubectl'
import {
BlueGreenDeployment,
BlueGreenManifests
} from '../../types/blueGreenTypes.js'
} from '../../types/blueGreenTypes'
import {RouteStrategy} from '../../types/routeStrategy.js'
import {RouteStrategy} from '../../types/routeStrategy'
import {
deployWithLabel,
getManifestObjects,
GREEN_LABEL_VALUE,
deployObjects
} from './blueGreenHelper.js'
import {setupSMI} from './smiBlueGreenHelper.js'
} from './blueGreenHelper'
import {setupSMI} from './smiBlueGreenHelper'
import {routeBlueGreenForDeploy} from './route.js'
import {DeployResult} from '../../types/deployResult.js'
import {routeBlueGreenForDeploy} from './route'
import {DeployResult} from '../../types/deployResult'
export async function deployBlueGreen(
kubectl: Kubectl,
@@ -1,27 +1,27 @@
import {vi} from 'vitest'
import {getManifestObjects, GREEN_LABEL_VALUE} from './blueGreenHelper.js'
import * as bgHelper from './blueGreenHelper.js'
import {getManifestObjects, GREEN_LABEL_VALUE} from './blueGreenHelper'
import * as bgHelper from './blueGreenHelper'
import {
getUpdatedBlueGreenIngress,
isIngressRouted,
validateIngresses
} from './ingressBlueGreenHelper.js'
import {Kubectl} from '../../types/kubectl.js'
import * as fileHelper from '../../utilities/fileUtils.js'
} from './ingressBlueGreenHelper'
import {Kubectl} from '../../types/kubectl'
import * as fileHelper from '../../utilities/fileUtils'
const betaFilepath = ['test/unit/manifests/test-ingress.yml']
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
const kubectl = new Kubectl('')
vi.mock('../../types/kubectl')
jest.mock('../../types/kubectl')
describe('ingress blue green helpers', () => {
let testObjects
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
//@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath)
vi.spyOn(fileHelper, 'writeObjectsToFile').mockImplementationOnce(() => [
''
])
jest
.spyOn(fileHelper, 'writeObjectsToFile')
.mockImplementationOnce(() => [''])
})
test('it should correctly classify ingresses', () => {
@@ -72,7 +72,7 @@ describe('ingress blue green helpers', () => {
test('it should validate ingresses', async () => {
// what if nothing gets returned from fetchResource?
vi.spyOn(bgHelper, 'fetchResource').mockResolvedValue(null)
jest.spyOn(bgHelper, 'fetchResource').mockImplementation()
let validResponse = await validateIngresses(
kubectl,
testObjects.ingressEntityList,
@@ -89,9 +89,9 @@ describe('ingress blue green helpers', () => {
const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = GREEN_LABEL_VALUE
mockIngress.metadata.labels = mockLabels
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve(mockIngress)
)
jest
.spyOn(bgHelper, 'fetchResource')
.mockImplementation(() => Promise.resolve(mockIngress))
validResponse = await validateIngresses(
kubectl,
testObjects.ingressEntityList,
@@ -1,12 +1,12 @@
import * as core from '@actions/core'
import {K8sIngress} from '../../types/k8sObject.js'
import {K8sIngress} from '../../types/k8sObject'
import {
addBlueGreenLabelsAndAnnotations,
BLUE_GREEN_VERSION_LABEL,
GREEN_LABEL_VALUE,
fetchResource
} from './blueGreenHelper.js'
import {Kubectl} from '../../types/kubectl.js'
} from './blueGreenHelper'
import {Kubectl} from '../../types/kubectl'
const BACKEND = 'backend'
+53 -47
View File
@@ -1,22 +1,20 @@
import {vi} from 'vitest'
import type {MockInstance} from 'vitest'
import {getManifestObjects} from './blueGreenHelper.js'
import {getManifestObjects} from './blueGreenHelper'
import {
promoteBlueGreenIngress,
promoteBlueGreenService,
promoteBlueGreenSMI
} from './promote.js'
import {TrafficSplitObject} from '../../types/k8sObject.js'
import * as servicesTester from './serviceBlueGreenHelper.js'
import {Kubectl} from '../../types/kubectl.js'
import {MAX_VAL, MIN_VAL, TRAFFIC_SPLIT_OBJECT} from './smiBlueGreenHelper.js'
import * as smiTester from './smiBlueGreenHelper.js'
import * as bgHelper from './blueGreenHelper.js'
} from './promote'
import {TrafficSplitObject} from '../../types/k8sObject'
import * as servicesTester from './serviceBlueGreenHelper'
import {Kubectl} from '../../types/kubectl'
import {MAX_VAL, MIN_VAL, TRAFFIC_SPLIT_OBJECT} from './smiBlueGreenHelper'
import * as smiTester from './smiBlueGreenHelper'
import * as bgHelper from './blueGreenHelper'
import {ExecOutput} from '@actions/exec'
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
vi.mock('../../types/kubectl')
jest.mock('../../types/kubectl')
// Shared variables used across all test suites
let testObjects: any
@@ -44,12 +42,13 @@ const mockBgDeployment = {
}
describe('promote tests', () => {
let kubectlApplySpy: MockInstance
let kubectlApplySpy: jest.SpyInstance
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
//@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath)
kubectlApplySpy = vi.spyOn(kubectl, 'apply')
kubectlApplySpy = jest.spyOn(kubectl, 'apply')
})
test('promote blue/green ingress', async () => {
@@ -58,7 +57,7 @@ describe('promote tests', () => {
const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.GREEN_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({
kind: 'Ingress',
spec: {},
@@ -83,7 +82,7 @@ describe('promote tests', () => {
test('fail to promote invalid blue/green ingress', async () => {
const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.NONE_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({
kind: 'Ingress',
spec: {},
@@ -101,7 +100,7 @@ describe('promote tests', () => {
const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.GREEN_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({
kind: 'Service',
spec: {selector: mockLabels},
@@ -121,16 +120,16 @@ describe('promote tests', () => {
test('fail to promote invalid blue/green service', async () => {
const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.NONE_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({
kind: 'Service',
spec: {},
metadata: {labels: mockLabels, name: 'nginx-ingress-green'}
})
)
vi.spyOn(servicesTester, 'validateServicesState').mockImplementationOnce(
() => Promise.resolve(false)
)
jest
.spyOn(servicesTester, 'validateServicesState')
.mockImplementationOnce(() => Promise.resolve(false))
await expect(
promoteBlueGreenService(kubectl, testObjects)
@@ -165,9 +164,9 @@ describe('promote tests', () => {
]
}
}
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve(mockTsObject)
)
jest
.spyOn(bgHelper, 'fetchResource')
.mockImplementation(() => Promise.resolve(mockTsObject))
const deployResult = await promoteBlueGreenSMI(kubectl, testObjects)
@@ -183,11 +182,11 @@ describe('promote tests', () => {
test('promote blue/green SMI with bad trafficsplit', async () => {
const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.NONE_LABEL_VALUE
vi.spyOn(smiTester, 'validateTrafficSplitsState').mockImplementation(() =>
Promise.resolve(false)
)
jest
.spyOn(smiTester, 'validateTrafficSplitsState')
.mockImplementation(() => Promise.resolve(false))
await expect(promoteBlueGreenSMI(kubectl, testObjects)).rejects.toThrow()
expect(promoteBlueGreenSMI(kubectl, testObjects)).rejects.toThrow()
})
// Consolidated error tests
@@ -199,7 +198,7 @@ describe('promote tests', () => {
const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] =
bgHelper.GREEN_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({
kind: 'Ingress',
spec: {},
@@ -215,16 +214,16 @@ describe('promote tests', () => {
const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] =
bgHelper.GREEN_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({
kind: 'Service',
spec: {selector: mockLabels},
metadata: {labels: mockLabels, name: 'nginx-service-green'}
})
)
vi.spyOn(servicesTester, 'validateServicesState').mockResolvedValue(
true
)
jest
.spyOn(servicesTester, 'validateServicesState')
.mockResolvedValue(true)
}
},
{
@@ -247,10 +246,12 @@ describe('promote tests', () => {
]
}
}
vi.spyOn(bgHelper, 'fetchResource').mockResolvedValue(mockTsObject)
vi.spyOn(smiTester, 'validateTrafficSplitsState').mockResolvedValue(
true
)
jest
.spyOn(bgHelper, 'fetchResource')
.mockResolvedValue(mockTsObject)
jest
.spyOn(smiTester, 'validateTrafficSplitsState')
.mockResolvedValue(true)
}
}
])('$name', async ({fn, setup}) => {
@@ -278,12 +279,15 @@ describe('promote tests', () => {
// Timeout tests
describe('promote timeout tests', () => {
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
// @ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath)
})
const mockDeployWithLabel = () =>
vi.spyOn(bgHelper, 'deployWithLabel').mockResolvedValue(mockBgDeployment)
jest
.spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment)
const setupFetchResource = (
kind: string,
@@ -293,7 +297,7 @@ describe('promote timeout tests', () => {
const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = labelValue
vi.spyOn(bgHelper, 'fetchResource').mockResolvedValue({
jest.spyOn(bgHelper, 'fetchResource').mockResolvedValue({
kind,
spec: {},
metadata: {labels: mockLabels, name}
@@ -326,9 +330,9 @@ describe('promote timeout tests', () => {
'nginx-service-green',
bgHelper.GREEN_LABEL_VALUE
)
vi.spyOn(servicesTester, 'validateServicesState').mockResolvedValue(
true
)
jest
.spyOn(servicesTester, 'validateServicesState')
.mockResolvedValue(true)
}
},
{
@@ -355,10 +359,12 @@ describe('promote timeout tests', () => {
}
}
vi.spyOn(bgHelper, 'fetchResource').mockResolvedValue(mockTsObject)
vi.spyOn(smiTester, 'validateTrafficSplitsState').mockResolvedValue(
true
)
jest
.spyOn(bgHelper, 'fetchResource')
.mockResolvedValue(mockTsObject)
jest
.spyOn(smiTester, 'validateTrafficSplitsState')
.mockResolvedValue(true)
}
}
])('$name', async ({fn, timeout, setup}) => {
+6 -6
View File
@@ -1,13 +1,13 @@
import * as core from '@actions/core'
import {Kubectl} from '../../types/kubectl.js'
import {Kubectl} from '../../types/kubectl'
import {BlueGreenDeployment} from '../../types/blueGreenTypes.js'
import {deployWithLabel, NONE_LABEL_VALUE} from './blueGreenHelper.js'
import {BlueGreenDeployment} from '../../types/blueGreenTypes'
import {deployWithLabel, NONE_LABEL_VALUE} from './blueGreenHelper'
import {validateIngresses} from './ingressBlueGreenHelper.js'
import {validateServicesState} from './serviceBlueGreenHelper.js'
import {validateTrafficSplitsState} from './smiBlueGreenHelper.js'
import {validateIngresses} from './ingressBlueGreenHelper'
import {validateServicesState} from './serviceBlueGreenHelper'
import {validateTrafficSplitsState} from './smiBlueGreenHelper'
export async function promoteBlueGreenIngress(
kubectl: Kubectl,
+37 -36
View File
@@ -1,23 +1,21 @@
import {vi} from 'vitest'
import type {MockInstance} from 'vitest'
import {getManifestObjects} from './blueGreenHelper.js'
import {Kubectl} from '../../types/kubectl.js'
import {getManifestObjects} from './blueGreenHelper'
import {Kubectl} from '../../types/kubectl'
import * as TSutils from '../../utilities/trafficSplitUtils.js'
import * as TSutils from '../../utilities/trafficSplitUtils'
import {
rejectBlueGreenIngress,
rejectBlueGreenService,
rejectBlueGreenSMI
} from './reject.js'
import * as bgHelper from './blueGreenHelper.js'
import * as routeHelper from './route.js'
} from './reject'
import * as bgHelper from './blueGreenHelper'
import * as routeHelper from './route'
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
const kubectl = new Kubectl('')
const TEST_TIMEOUT_SHORT = '60s'
const TEST_TIMEOUT_LONG = '120s'
vi.mock('../../types/kubectl')
jest.mock('../../types/kubectl')
// Shared mock objects following DRY principle
const mockSuccessResult = {
@@ -56,13 +54,14 @@ const mockDeleteResult = [
describe('reject tests', () => {
let testObjects: any
let kubectlApplySpy: MockInstance
let kubectlApplySpy: jest.SpyInstance
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
vi.restoreAllMocks()
//@ts-ignore
Kubectl.mockClear()
jest.restoreAllMocks()
testObjects = getManifestObjects(ingressFilepath)
kubectlApplySpy = vi.spyOn(kubectl, 'apply')
kubectlApplySpy = jest.spyOn(kubectl, 'apply')
})
test('reject blue/green ingress', async () => {
@@ -90,13 +89,13 @@ describe('reject tests', () => {
test('reject blue/green ingress with timeout', async () => {
// Mock routeBlueGreenIngressUnchanged and deleteGreenObjects
vi.spyOn(routeHelper, 'routeBlueGreenIngressUnchanged').mockResolvedValue(
mockBgDeployment
)
jest
.spyOn(routeHelper, 'routeBlueGreenIngressUnchanged')
.mockResolvedValue(mockBgDeployment)
vi.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue(
mockDeleteResult
)
jest
.spyOn(bgHelper, 'deleteGreenObjects')
.mockResolvedValue(mockDeleteResult)
const value = await rejectBlueGreenIngress(
kubectl,
@@ -139,9 +138,9 @@ describe('reject tests', () => {
test('reject blue/green service', async () => {
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
vi.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue(
mockDeleteResult
)
jest
.spyOn(bgHelper, 'deleteGreenObjects')
.mockResolvedValue(mockDeleteResult)
const value = await rejectBlueGreenService(
kubectl,
@@ -164,7 +163,7 @@ describe('reject tests', () => {
test('reject blue/green service with timeout', async () => {
// Mock routeBlueGreenService and deleteGreenObjects
vi.spyOn(routeHelper, 'routeBlueGreenService').mockResolvedValue({
jest.spyOn(routeHelper, 'routeBlueGreenService').mockResolvedValue({
deployResult: {
execResult: {stdout: '', stderr: '', exitCode: 0},
manifestFiles: []
@@ -181,7 +180,9 @@ describe('reject tests', () => {
]
})
vi.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue([
jest
.spyOn(bgHelper, 'deleteGreenObjects')
.mockResolvedValue([
{name: 'nginx-deployment-green', kind: 'Deployment'}
])
@@ -212,9 +213,9 @@ describe('reject tests', () => {
// Mock kubectl.apply to return successful result
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(() =>
Promise.resolve('v1alpha3')
)
jest
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
.mockImplementation(() => Promise.resolve('v1alpha3'))
const rejectResult = await rejectBlueGreenSMI(kubectl, testObjects)
expect(rejectResult.deleteResult).toHaveLength(2)
})
@@ -225,27 +226,27 @@ describe('reject tests', () => {
name: 'should throw error when kubectl apply fails during blue/green ingress rejection',
fn: () => rejectBlueGreenIngress(kubectl, testObjects),
setup: () => {
vi.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue(
mockDeleteResult
)
jest
.spyOn(bgHelper, 'deleteGreenObjects')
.mockResolvedValue(mockDeleteResult)
}
},
{
name: 'should throw error when kubectl apply fails during blue/green service rejection',
fn: () => rejectBlueGreenService(kubectl, testObjects),
setup: () => {
vi.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue(
mockDeleteResult
)
jest
.spyOn(bgHelper, 'deleteGreenObjects')
.mockResolvedValue(mockDeleteResult)
}
},
{
name: 'should throw error when kubectl apply fails during blue/green SMI rejection',
fn: () => rejectBlueGreenSMI(kubectl, testObjects),
setup: () => {
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(
() => Promise.resolve('v1alpha3')
)
jest
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
.mockImplementation(() => Promise.resolve('v1alpha3'))
}
}
])('$name', async ({fn, setup}) => {
+7 -7
View File
@@ -1,14 +1,14 @@
import {K8sDeleteObject} from '../../types/k8sObject.js'
import {Kubectl} from '../../types/kubectl.js'
import {K8sDeleteObject} from '../../types/k8sObject'
import {Kubectl} from '../../types/kubectl'
import {
BlueGreenDeployment,
BlueGreenManifests,
BlueGreenRejectResult
} from '../../types/blueGreenTypes.js'
import {deleteGreenObjects, NONE_LABEL_VALUE} from './blueGreenHelper.js'
import {routeBlueGreenSMI} from './route.js'
import {cleanupSMI} from './smiBlueGreenHelper.js'
import {routeBlueGreenIngressUnchanged, routeBlueGreenService} from './route.js'
} from '../../types/blueGreenTypes'
import {deleteGreenObjects, NONE_LABEL_VALUE} from './blueGreenHelper'
import {routeBlueGreenSMI} from './route'
import {cleanupSMI} from './smiBlueGreenHelper'
import {routeBlueGreenIngressUnchanged, routeBlueGreenService} from './route'
export async function rejectBlueGreenIngress(
kubectl: Kubectl,
+43 -51
View File
@@ -1,28 +1,24 @@
import {vi} from 'vitest'
import type {MockInstance} from 'vitest'
import {K8sIngress, TrafficSplitObject} from '../../types/k8sObject.js'
import {Kubectl} from '../../types/kubectl.js'
import * as fileHelper from '../../utilities/fileUtils.js'
import * as TSutils from '../../utilities/trafficSplitUtils.js'
import {RouteStrategy} from '../../types/routeStrategy.js'
import {BlueGreenManifests} from '../../types/blueGreenTypes.js'
import {K8sIngress, TrafficSplitObject} from '../../types/k8sObject'
import {Kubectl} from '../../types/kubectl'
import * as fileHelper from '../../utilities/fileUtils'
import * as TSutils from '../../utilities/trafficSplitUtils'
import {RouteStrategy} from '../../types/routeStrategy'
import {BlueGreenManifests} from '../../types/blueGreenTypes'
import {
BLUE_GREEN_VERSION_LABEL,
getManifestObjects,
GREEN_LABEL_VALUE
} from './blueGreenHelper.js'
import * as bgHelper from './blueGreenHelper.js'
import * as smiHelper from './smiBlueGreenHelper.js'
} from './blueGreenHelper'
import {
routeBlueGreenIngress,
routeBlueGreenService,
routeBlueGreenForDeploy,
routeBlueGreenSMI,
routeBlueGreenIngressUnchanged
} from './route.js'
} from './route'
vi.mock('../../types/kubectl')
jest.mock('../../types/kubectl')
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
const kc = new Kubectl('')
@@ -41,15 +37,16 @@ const mockFailureResult = {
describe('route function tests', () => {
let testObjects: BlueGreenManifests
let kubectlApplySpy: MockInstance
let kubectlApplySpy: jest.SpyInstance
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
//@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath)
kubectlApplySpy = vi.spyOn(kc, 'apply')
vi.spyOn(fileHelper, 'writeObjectsToFile').mockImplementationOnce(() => [
''
])
kubectlApplySpy = jest.spyOn(kc, 'apply')
jest
.spyOn(fileHelper, 'writeObjectsToFile')
.mockImplementationOnce(() => [''])
})
test('correctly prepares blue/green ingresses for deployment', async () => {
@@ -99,9 +96,9 @@ describe('route function tests', () => {
})
test('correctly identifies route pattern and acts accordingly', async () => {
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(() =>
Promise.resolve('v1alpha3')
)
jest
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
.mockImplementation(() => Promise.resolve('v1alpha3'))
const ingressResult = await routeBlueGreenForDeploy(
kc,
@@ -167,9 +164,9 @@ describe('route function tests', () => {
testObjects.serviceEntityList
),
setup: () => {
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(
() => Promise.resolve('v1alpha3')
)
jest
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
.mockImplementation(() => Promise.resolve('v1alpha3'))
}
},
{
@@ -197,23 +194,24 @@ describe('route timeout tests', () => {
let testObjects: BlueGreenManifests
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
//@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath)
vi.spyOn(fileHelper, 'writeObjectsToFile').mockImplementationOnce(() => [
''
])
jest
.spyOn(fileHelper, 'writeObjectsToFile')
.mockImplementationOnce(() => [''])
})
afterEach(() => {
vi.restoreAllMocks()
jest.restoreAllMocks()
})
test('routeBlueGreenService with timeout', async () => {
const timeout = '240s'
// Mock deployObjects to capture timeout parameter
const deployObjectsSpy = vi
.spyOn(bgHelper, 'deployObjects')
const deployObjectsSpy = jest
.spyOn(require('./blueGreenHelper'), 'deployObjects')
.mockResolvedValue({
execResult: mockSuccessResult,
manifestFiles: []
@@ -239,29 +237,23 @@ describe('route timeout tests', () => {
test('routeBlueGreenSMI with timeout', async () => {
const timeout = '300s'
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(() =>
Promise.resolve('v1alpha3')
)
jest
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
.mockImplementation(() => Promise.resolve('v1alpha3'))
// Mock deployObjects and createTrafficSplitObject to capture timeout parameter
const deployObjectsSpy = vi
.spyOn(bgHelper, 'deployObjects')
const deployObjectsSpy = jest
.spyOn(require('./blueGreenHelper'), 'deployObjects')
.mockResolvedValue({
execResult: mockSuccessResult,
manifestFiles: []
})
const createTrafficSplitSpy = vi
.spyOn(smiHelper, 'createTrafficSplitObject')
const createTrafficSplitSpy = jest
.spyOn(require('./smiBlueGreenHelper'), 'createTrafficSplitObject')
.mockResolvedValue({
apiVersion: 'split.smi-spec.io/v1alpha3',
kind: 'TrafficSplit',
metadata: {
name: 'nginx-service-trafficsplit',
labels: new Map(),
annotations: new Map()
},
spec: {service: 'nginx-service', backends: []}
metadata: {name: 'nginx-service-trafficsplit'},
spec: {backends: []}
})
const value = await routeBlueGreenSMI(
@@ -292,8 +284,8 @@ describe('route timeout tests', () => {
const timeout = '180s'
// Mock deployObjects to capture timeout parameter
const deployObjectsSpy = vi
.spyOn(bgHelper, 'deployObjects')
const deployObjectsSpy = jest
.spyOn(require('./blueGreenHelper'), 'deployObjects')
.mockResolvedValue({
execResult: mockSuccessResult,
manifestFiles: []
@@ -317,8 +309,8 @@ describe('route timeout tests', () => {
})
test('route functions without timeout should pass undefined', async () => {
const deployObjectsSpy = vi
.spyOn(bgHelper, 'deployObjects')
const deployObjectsSpy = jest
.spyOn(require('./blueGreenHelper'), 'deployObjects')
.mockResolvedValue({
execResult: mockSuccessResult,
manifestFiles: []
+10 -10
View File
@@ -1,26 +1,26 @@
import {sleep} from '../../utilities/timeUtils.js'
import {RouteStrategy} from '../../types/routeStrategy.js'
import {Kubectl} from '../../types/kubectl.js'
import {sleep} from '../../utilities/timeUtils'
import {RouteStrategy} from '../../types/routeStrategy'
import {Kubectl} from '../../types/kubectl'
import {
BlueGreenDeployment,
BlueGreenManifests
} from '../../types/blueGreenTypes.js'
} from '../../types/blueGreenTypes'
import {
getManifestObjects,
GREEN_LABEL_VALUE,
deployObjects
} from './blueGreenHelper.js'
} from './blueGreenHelper'
import {
getUpdatedBlueGreenIngress,
isIngressRouted
} from './ingressBlueGreenHelper.js'
import {getUpdatedBlueGreenService} from './serviceBlueGreenHelper.js'
import {createTrafficSplitObject} from './smiBlueGreenHelper.js'
} from './ingressBlueGreenHelper'
import {getUpdatedBlueGreenService} from './serviceBlueGreenHelper'
import {createTrafficSplitObject} from './smiBlueGreenHelper'
import * as core from '@actions/core'
import {K8sObject, TrafficSplitObject} from '../../types/k8sObject.js'
import {getBufferTime} from '../../inputUtils.js'
import {K8sObject, TrafficSplitObject} from '../../types/k8sObject'
import {getBufferTime} from '../../inputUtils'
export async function routeBlueGreenForDeploy(
kubectl: Kubectl,
@@ -1,26 +1,26 @@
import {vi} from 'vitest'
import * as core from '@actions/core'
import {
BLUE_GREEN_VERSION_LABEL,
getManifestObjects,
GREEN_LABEL_VALUE
} from './blueGreenHelper.js'
import * as bgHelper from './blueGreenHelper.js'
import {Kubectl} from '../../types/kubectl.js'
} from './blueGreenHelper'
import * as bgHelper from './blueGreenHelper'
import {Kubectl} from '../../types/kubectl'
import {
getServiceSpecLabel,
getUpdatedBlueGreenService,
validateServicesState
} from './serviceBlueGreenHelper.js'
} from './serviceBlueGreenHelper'
let testObjects
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
vi.mock('../../types/kubectl')
jest.mock('../../types/kubectl')
const kubectl = new Kubectl('')
describe('blue/green service helper tests', () => {
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
//@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath)
})
@@ -42,7 +42,7 @@ describe('blue/green service helper tests', () => {
mockLabels[BLUE_GREEN_VERSION_LABEL] = bgHelper.GREEN_LABEL_VALUE
const mockSelectors = new Map<string, string>()
mockSelectors[BLUE_GREEN_VERSION_LABEL] = GREEN_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({
kind: 'Service',
spec: {selector: mockSelectors},
@@ -1,12 +1,12 @@
import * as core from '@actions/core'
import {K8sServiceObject} from '../../types/k8sObject.js'
import {Kubectl} from '../../types/kubectl.js'
import {K8sServiceObject} from '../../types/k8sObject'
import {Kubectl} from '../../types/kubectl'
import {
addBlueGreenLabelsAndAnnotations,
BLUE_GREEN_VERSION_LABEL,
fetchResource,
GREEN_LABEL_VALUE
} from './blueGreenHelper.js'
} from './blueGreenHelper'
// add green labels to configure existing service
export function getUpdatedBlueGreenService(
@@ -1,16 +1,15 @@
import {vi} from 'vitest'
import {TrafficSplitObject} from '../../types/k8sObject.js'
import {Kubectl} from '../../types/kubectl.js'
import * as fileHelper from '../../utilities/fileUtils.js'
import * as TSutils from '../../utilities/trafficSplitUtils.js'
import {TrafficSplitObject} from '../../types/k8sObject'
import {Kubectl} from '../../types/kubectl'
import * as fileHelper from '../../utilities/fileUtils'
import * as TSutils from '../../utilities/trafficSplitUtils'
import {BlueGreenManifests} from '../../types/blueGreenTypes.js'
import {BlueGreenManifests} from '../../types/blueGreenTypes'
import {
BLUE_GREEN_VERSION_LABEL,
getManifestObjects,
GREEN_LABEL_VALUE,
NONE_LABEL_VALUE
} from './blueGreenHelper.js'
} from './blueGreenHelper'
import {
cleanupSMI,
@@ -22,10 +21,10 @@ import {
setupSMI,
TRAFFIC_SPLIT_OBJECT,
validateTrafficSplitsState
} from './smiBlueGreenHelper.js'
import * as bgHelper from './blueGreenHelper.js'
} from './smiBlueGreenHelper'
import * as bgHelper from './blueGreenHelper'
vi.mock('../../types/kubectl')
jest.mock('../../types/kubectl')
const kc = new Kubectl('')
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
@@ -69,20 +68,21 @@ const mockTsObject: TrafficSplitObject = {
describe('SMI Helper tests', () => {
let testObjects: BlueGreenManifests
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
//@ts-ignore
Kubectl.mockClear()
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(() =>
Promise.resolve('')
)
jest
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
.mockImplementation(() => Promise.resolve(''))
testObjects = getManifestObjects(ingressFilepath)
vi.spyOn(fileHelper, 'writeObjectsToFile').mockImplementationOnce(() => [
''
])
jest
.spyOn(fileHelper, 'writeObjectsToFile')
.mockImplementationOnce(() => [''])
})
test('setupSMI tests', async () => {
vi.spyOn(kc, 'apply').mockResolvedValue(mockSuccessResult)
jest.spyOn(kc, 'apply').mockResolvedValue(mockSuccessResult)
const smiResults = await setupSMI(kc, testObjects.serviceEntityList)
@@ -174,9 +174,9 @@ describe('SMI Helper tests', () => {
})
test('validateTrafficSplitsState', async () => {
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve(mockTsObject)
)
jest
.spyOn(bgHelper, 'fetchResource')
.mockImplementation(() => Promise.resolve(mockTsObject))
let valResult = await validateTrafficSplitsState(
kc,
@@ -187,9 +187,9 @@ describe('SMI Helper tests', () => {
const mockTsCopy = JSON.parse(JSON.stringify(mockTsObject))
mockTsCopy.spec.backends[0].weight = MAX_VAL
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve(mockTsCopy)
)
jest
.spyOn(bgHelper, 'fetchResource')
.mockImplementation(() => Promise.resolve(mockTsCopy))
valResult = await validateTrafficSplitsState(
kc,
@@ -197,7 +197,7 @@ describe('SMI Helper tests', () => {
)
expect(valResult).toBe(false)
vi.spyOn(bgHelper, 'fetchResource').mockResolvedValue(null)
jest.spyOn(bgHelper, 'fetchResource').mockImplementation()
valResult = await validateTrafficSplitsState(
kc,
testObjects.serviceEntityList
@@ -218,7 +218,7 @@ describe('SMI Helper tests', () => {
name: 'should throw error when kubectl apply fails during SMI setup',
fn: () => setupSMI(kc, testObjects.serviceEntityList),
setup: () => {
vi.spyOn(kc, 'apply').mockResolvedValue(mockFailureResult)
jest.spyOn(kc, 'apply').mockResolvedValue(mockFailureResult)
}
}
])('$name', async ({fn, setup}) => {
@@ -229,7 +229,7 @@ describe('SMI Helper tests', () => {
// Timeout-specific tests
test('setupSMI with timeout test', async () => {
const deployObjectsSpy = vi
const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects')
.mockResolvedValue({
execResult: mockSuccessResult,
@@ -257,7 +257,7 @@ describe('SMI Helper tests', () => {
})
test('createTrafficSplitObject with timeout test', async () => {
const deleteObjectsSpy = vi
const deleteObjectsSpy = jest
.spyOn(bgHelper, 'deleteObjects')
.mockResolvedValue()
@@ -288,7 +288,7 @@ describe('SMI Helper tests', () => {
})
test('createTrafficSplitObject with GREEN_LABEL_VALUE and timeout test', async () => {
const deleteObjectsSpy = vi
const deleteObjectsSpy = jest
.spyOn(bgHelper, 'deleteObjects')
.mockResolvedValue()
@@ -321,7 +321,7 @@ describe('SMI Helper tests', () => {
})
test('cleanupSMI with timeout test', async () => {
const deleteObjectsSpy = vi
const deleteObjectsSpy = jest
.spyOn(bgHelper, 'deleteObjects')
.mockResolvedValue()
@@ -352,7 +352,7 @@ describe('SMI Helper tests', () => {
})
test('setupSMI without timeout test', async () => {
const deployObjectsSpy = vi
const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects')
.mockResolvedValue({
execResult: mockSuccessResult,
@@ -375,7 +375,7 @@ describe('SMI Helper tests', () => {
})
test('createTrafficSplitObject without timeout test', async () => {
const deleteObjectsSpy = vi
const deleteObjectsSpy = jest
.spyOn(bgHelper, 'deleteObjects')
.mockResolvedValue()
@@ -398,7 +398,7 @@ describe('SMI Helper tests', () => {
})
test('cleanupSMI without timeout test', async () => {
const deleteObjectsSpy = vi
const deleteObjectsSpy = jest
.spyOn(bgHelper, 'deleteObjects')
.mockResolvedValue()
@@ -1,6 +1,6 @@
import * as core from '@actions/core'
import {Kubectl} from '../../types/kubectl.js'
import * as kubectlUtils from '../../utilities/trafficSplitUtils.js'
import {Kubectl} from '../../types/kubectl'
import * as kubectlUtils from '../../utilities/trafficSplitUtils'
import {
deleteObjects,
deployObjects,
@@ -11,15 +11,15 @@ import {
GREEN_SUFFIX,
NONE_LABEL_VALUE,
STABLE_SUFFIX
} from './blueGreenHelper.js'
import {BlueGreenDeployment} from '../../types/blueGreenTypes.js'
} from './blueGreenHelper'
import {BlueGreenDeployment} from '../../types/blueGreenTypes'
import {
K8sDeleteObject,
K8sObject,
TrafficSplitObject
} from '../../types/k8sObject.js'
import {DeployResult} from '../../types/deployResult.js'
import {inputAnnotations} from '../../inputUtils.js'
} from '../../types/k8sObject'
import {DeployResult} from '../../types/deployResult'
import {inputAnnotations} from '../../inputUtils'
export const TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX = '-trafficsplit'
export const TRAFFIC_SPLIT_OBJECT = 'TrafficSplit'
+6 -6
View File
@@ -1,4 +1,4 @@
import {Kubectl} from '../../types/kubectl.js'
import {Kubectl} from '../../types/kubectl'
import * as fs from 'fs'
import * as yaml from 'js-yaml'
import * as core from '@actions/core'
@@ -7,15 +7,15 @@ import {
isDeploymentEntity,
isServiceEntity,
KubernetesWorkload
} from '../../types/kubernetesTypes.js'
import * as utils from '../../utilities/manifestUpdateUtils.js'
} from '../../types/kubernetesTypes'
import * as utils from '../../utilities/manifestUpdateUtils'
import {
updateObjectAnnotations,
updateObjectLabels,
updateSelectorLabels
} from '../../utilities/manifestUpdateUtils.js'
import {updateSpecLabels} from '../../utilities/manifestSpecLabelUtils.js'
import {checkForErrors} from '../../utilities/kubectlUtils.js'
} from '../../utilities/manifestUpdateUtils'
import {updateSpecLabels} from '../../utilities/manifestSpecLabelUtils'
import {checkForErrors} from '../../utilities/kubectlUtils'
export const CANARY_VERSION_LABEL = 'workflow/version'
const BASELINE_SUFFIX = '-baseline'
@@ -1,15 +1,11 @@
import {vi} from 'vitest'
import type {MockInstance} from 'vitest'
vi.mock('@actions/core')
import * as core from '@actions/core'
import {Kubectl} from '../../types/kubectl.js'
import {Kubectl} from '../../types/kubectl'
import {
deployPodCanary,
calculateReplicaCountForCanary
} from './podCanaryHelper.js'
} from './podCanaryHelper'
vi.mock('../../types/kubectl')
jest.mock('../../types/kubectl')
const kc = new Kubectl('')
@@ -39,17 +35,18 @@ const TIMEOUT_300S = '300s'
describe('Pod Canary Helper tests', () => {
let mockFilePaths: string[]
let kubectlApplySpy: MockInstance
let kubectlApplySpy: jest.SpyInstance
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
vi.restoreAllMocks()
//@ts-ignore
Kubectl.mockClear()
jest.restoreAllMocks()
mockFilePaths = testManifestFiles
kubectlApplySpy = vi.spyOn(kc, 'apply')
kubectlApplySpy = jest.spyOn(kc, 'apply')
// Mock core.getInput with default values
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) {
case 'percentage':
return VALID_PERCENTAGE.toString()
@@ -64,7 +61,7 @@ describe('Pod Canary Helper tests', () => {
})
afterEach(() => {
vi.restoreAllMocks()
jest.restoreAllMocks()
kubectlApplySpy.mockClear()
})
@@ -117,7 +114,7 @@ describe('Pod Canary Helper tests', () => {
})
test('should throw error for invalid low percentage', async () => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
if (name === 'percentage') return INVALID_LOW_PERCENTAGE.toString()
return ''
})
@@ -130,7 +127,7 @@ describe('Pod Canary Helper tests', () => {
})
test('should throw error for invalid high percentage', async () => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
if (name === 'percentage') return INVALID_HIGH_PERCENTAGE.toString()
return ''
})
@@ -146,7 +143,7 @@ describe('Pod Canary Helper tests', () => {
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
// Test minimum valid percentage
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
if (name === 'percentage') return MIN_PERCENTAGE.toString()
return ''
})
@@ -155,7 +152,7 @@ describe('Pod Canary Helper tests', () => {
expect(resultMin.execResult).toEqual(mockSuccessResult)
// Test maximum valid percentage
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
if (name === 'percentage') return MAX_PERCENTAGE.toString()
return ''
})
@@ -165,7 +162,7 @@ describe('Pod Canary Helper tests', () => {
})
test('should handle force deployment option', async () => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) {
case 'percentage':
return VALID_PERCENTAGE.toString()
@@ -189,7 +186,7 @@ describe('Pod Canary Helper tests', () => {
})
test('should handle server-side apply option', async () => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) {
case 'percentage':
return VALID_PERCENTAGE.toString()
@@ -1,15 +1,15 @@
import {Kubectl} from '../../types/kubectl.js'
import {Kubectl} from '../../types/kubectl'
import * as core from '@actions/core'
import * as fs from 'fs'
import * as yaml from 'js-yaml'
import * as fileHelper from '../../utilities/fileUtils.js'
import * as canaryDeploymentHelper from './canaryHelper.js'
import {isDeploymentEntity} from '../../types/kubernetesTypes.js'
import {getReplicaCount} from '../../utilities/manifestUpdateUtils.js'
import {DeployResult} from '../../types/deployResult.js'
import {K8sObject} from '../../types/k8sObject.js'
import {checkForErrors} from '../../utilities/kubectlUtils.js'
import * as fileHelper from '../../utilities/fileUtils'
import * as canaryDeploymentHelper from './canaryHelper'
import {isDeploymentEntity} from '../../types/kubernetesTypes'
import {getReplicaCount} from '../../utilities/manifestUpdateUtils'
import {DeployResult} from '../../types/deployResult'
import {K8sObject} from '../../types/k8sObject'
import {checkForErrors} from '../../utilities/kubectlUtils'
export async function deployPodCanary(
filePaths: string[],
@@ -1,34 +1,13 @@
import {vi} from 'vitest'
import type {MockInstance} from 'vitest'
vi.mock('@actions/core', async (importOriginal) => {
const actual: any = await importOriginal()
return {
...actual,
getInput: vi.fn().mockReturnValue(''),
debug: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
setFailed: vi.fn(),
setOutput: vi.fn(),
group: vi
.fn()
.mockImplementation(
async (_name: string, fn: () => Promise<void>) => await fn()
)
}
})
import * as core from '@actions/core'
import * as fs from 'fs'
import {Kubectl} from '../../types/kubectl.js'
import {Kubectl} from '../../types/kubectl'
import {
deploySMICanary,
redirectTrafficToCanaryDeployment,
redirectTrafficToStableDeployment
} from './smiCanaryHelper.js'
} from './smiCanaryHelper'
vi.mock('../../types/kubectl')
jest.mock('../../types/kubectl')
const kc = new Kubectl('')
@@ -61,21 +40,22 @@ const TIMEOUT_240S = '240s'
describe('SMI Canary Helper tests', () => {
let mockFilePaths: string[]
let kubectlApplySpy: MockInstance
let kubectlExecuteCommandSpy: MockInstance
let kubectlApplySpy: jest.SpyInstance
let kubectlExecuteCommandSpy: jest.SpyInstance
beforeEach(() => {
vi.mocked(Kubectl).mockClear()
vi.restoreAllMocks()
//@ts-ignore
Kubectl.mockClear()
jest.restoreAllMocks()
mockFilePaths = testManifestFiles
kubectlApplySpy = vi.spyOn(kc, 'apply')
kubectlExecuteCommandSpy = vi
kubectlApplySpy = jest.spyOn(kc, 'apply')
kubectlExecuteCommandSpy = jest
.spyOn(kc, 'executeCommand')
.mockResolvedValue(mockExecuteCommandResult)
// Mock core.getInput with default values
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) {
case 'percentage':
return '50'
@@ -92,7 +72,7 @@ describe('SMI Canary Helper tests', () => {
})
afterEach(() => {
vi.restoreAllMocks()
jest.restoreAllMocks()
kubectlApplySpy.mockClear()
})
@@ -126,7 +106,7 @@ describe('SMI Canary Helper tests', () => {
})
test('should handle custom replica count from input', async () => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) {
case 'baseline-and-canary-replicas':
return VALID_REPLICA_COUNT.toString()
+10 -13
View File
@@ -1,20 +1,17 @@
import {Kubectl} from '../../types/kubectl.js'
import {Kubectl} from '../../types/kubectl'
import * as core from '@actions/core'
import * as fs from 'fs'
import * as yaml from 'js-yaml'
import * as fileHelper from '../../utilities/fileUtils.js'
import * as kubectlUtils from '../../utilities/trafficSplitUtils.js'
import * as canaryDeploymentHelper from './canaryHelper.js'
import * as podCanaryHelper from './podCanaryHelper.js'
import {
isDeploymentEntity,
isServiceEntity
} from '../../types/kubernetesTypes.js'
import {checkForErrors} from '../../utilities/kubectlUtils.js'
import {inputAnnotations} from '../../inputUtils.js'
import {DeployResult} from '../../types/deployResult.js'
import {K8sObject} from '../../types/k8sObject.js'
import * as fileHelper from '../../utilities/fileUtils'
import * as kubectlUtils from '../../utilities/trafficSplitUtils'
import * as canaryDeploymentHelper from './canaryHelper'
import * as podCanaryHelper from './podCanaryHelper'
import {isDeploymentEntity, isServiceEntity} from '../../types/kubernetesTypes'
import {checkForErrors} from '../../utilities/kubectlUtils'
import {inputAnnotations} from '../../inputUtils'
import {DeployResult} from '../../types/deployResult'
import {K8sObject} from '../../types/k8sObject'
const TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX = '-workflow-rollout'
const TRAFFIC_SPLIT_OBJECT = 'TrafficSplit'
+19 -19
View File
@@ -1,41 +1,41 @@
import * as fs from 'fs'
import * as yaml from 'js-yaml'
import * as canaryDeploymentHelper from './canary/canaryHelper.js'
import * as models from '../types/kubernetesTypes.js'
import {isDeploymentEntity} from '../types/kubernetesTypes.js'
import * as fileHelper from '../utilities/fileUtils.js'
import * as KubernetesManifestUtility from '../utilities/manifestStabilityUtils.js'
import {Kubectl, Resource} from '../types/kubectl.js'
import * as canaryDeploymentHelper from './canary/canaryHelper'
import * as models from '../types/kubernetesTypes'
import {isDeploymentEntity} from '../types/kubernetesTypes'
import * as fileHelper from '../utilities/fileUtils'
import * as KubernetesManifestUtility from '../utilities/manifestStabilityUtils'
import {Kubectl, Resource} from '../types/kubectl'
import {deployPodCanary} from './canary/podCanaryHelper.js'
import {deploySMICanary} from './canary/smiCanaryHelper.js'
import {DeploymentConfig} from '../types/deploymentConfig.js'
import {deployBlueGreen} from './blueGreen/deploy.js'
import {DeploymentStrategy} from '../types/deploymentStrategy.js'
import {deployPodCanary} from './canary/podCanaryHelper'
import {deploySMICanary} from './canary/smiCanaryHelper'
import {DeploymentConfig} from '../types/deploymentConfig'
import {deployBlueGreen} from './blueGreen/deploy'
import {DeploymentStrategy} from '../types/deploymentStrategy'
import * as core from '@actions/core'
import {
parseTrafficSplitMethod,
TrafficSplitMethod
} from '../types/trafficSplitMethod.js'
import {parseRouteStrategy} from '../types/routeStrategy.js'
} from '../types/trafficSplitMethod'
import {parseRouteStrategy} from '../types/routeStrategy'
import {ExecOutput} from '@actions/exec'
import {
getWorkflowAnnotationKeyLabel,
getWorkflowAnnotations,
cleanLabel
} from '../utilities/workflowAnnotationUtils.js'
} from '../utilities/workflowAnnotationUtils'
import {
annotateChildPods,
checkForErrors,
getLastSuccessfulRunSha
} from '../utilities/kubectlUtils.js'
} from '../utilities/kubectlUtils'
import {
getWorkflowFilePath,
normalizeWorkflowStrLabel
} from '../utilities/githubUtils.js'
import {getDeploymentConfig} from '../utilities/dockerUtils.js'
import {DeployResult} from '../types/deployResult.js'
import {ClusterType} from '../inputUtils.js'
} from '../utilities/githubUtils'
import {getDeploymentConfig} from '../utilities/dockerUtils'
import {DeployResult} from '../types/deployResult'
import {ClusterType} from '../inputUtils'
export async function deployManifests(
files: string[],
+1 -1
View File
@@ -1,4 +1,4 @@
import {Action, parseAction} from './action.js'
import {Action, parseAction} from './action'
describe('Action type', () => {
test('it has required values', () => {
+2 -2
View File
@@ -1,5 +1,5 @@
import {DeployResult} from './deployResult.js'
import {K8sObject, K8sDeleteObject} from './k8sObject.js'
import {DeployResult} from './deployResult'
import {K8sObject, K8sDeleteObject} from './k8sObject'
export interface BlueGreenDeployment {
deployResult: DeployResult
+1 -4
View File
@@ -1,7 +1,4 @@
import {
DeploymentStrategy,
parseDeploymentStrategy
} from './deploymentStrategy.js'
import {DeploymentStrategy, parseDeploymentStrategy} from './deploymentStrategy'
describe('Deployment strategy type', () => {
test('it has required values', () => {
+4 -7
View File
@@ -1,7 +1,4 @@
import {vi} from 'vitest'
vi.mock('@actions/exec')
import {DockerExec} from './docker.js'
import {DockerExec} from './docker'
import * as actions from '@actions/exec'
const dockerPath = 'dockerPath'
@@ -15,7 +12,7 @@ describe('Docker class', () => {
const execReturn = {exitCode: 0, stdout: 'Output', stderr: ''}
beforeEach(() => {
vi.spyOn(actions, 'getExecOutput').mockImplementation(async () => {
jest.spyOn(actions, 'getExecOutput').mockImplementation(async () => {
return execReturn
})
})
@@ -63,7 +60,7 @@ describe('Docker class', () => {
const execReturn = {exitCode: 3, stdout: '', stderr: ''}
beforeEach(() => {
vi.spyOn(actions, 'getExecOutput').mockImplementation(async () => {
jest.spyOn(actions, 'getExecOutput').mockImplementation(async () => {
return execReturn
})
})
@@ -83,7 +80,7 @@ describe('Docker class', () => {
const execReturn = {exitCode: 0, stdout: '', stderr: 'Output'}
beforeEach(() => {
vi.spyOn(actions, 'getExecOutput').mockImplementation(async () => {
jest.spyOn(actions, 'getExecOutput').mockImplementation(async () => {
return execReturn
})
})
+12 -18
View File
@@ -1,10 +1,4 @@
import {vi} from 'vitest'
vi.mock('@actions/exec')
vi.mock('@actions/io')
vi.mock('@actions/core')
vi.mock('@actions/tool-cache')
import {getKubectlPath, Kubectl} from './kubectl.js'
import {getKubectlPath, Kubectl} from './kubectl'
import * as exec from '@actions/exec'
import * as io from '@actions/io'
import * as core from '@actions/core'
@@ -15,27 +9,27 @@ describe('Kubectl path', () => {
const path = 'path'
it('gets the kubectl path', async () => {
vi.spyOn(core, 'getInput').mockImplementationOnce(() => '')
vi.spyOn(io, 'which').mockImplementationOnce(async () => path)
jest.spyOn(core, 'getInput').mockImplementationOnce(() => '')
jest.spyOn(io, 'which').mockImplementationOnce(async () => path)
expect(await getKubectlPath()).toBe(path)
})
it('gets the kubectl path with version', async () => {
vi.spyOn(core, 'getInput').mockImplementationOnce(() => version)
vi.spyOn(toolCache, 'find').mockImplementationOnce(() => path)
jest.spyOn(core, 'getInput').mockImplementationOnce(() => version)
jest.spyOn(toolCache, 'find').mockImplementationOnce(() => path)
expect(await getKubectlPath()).toBe(path)
})
it('throws if kubectl not found', async () => {
// without version
vi.spyOn(io, 'which').mockImplementationOnce(async () => '')
jest.spyOn(io, 'which').mockImplementationOnce(async () => '')
await expect(() => getKubectlPath()).rejects.toThrow()
// with verision
vi.spyOn(core, 'getInput').mockImplementationOnce(() => '')
vi.spyOn(io, 'which').mockImplementationOnce(async () => '')
jest.spyOn(core, 'getInput').mockImplementationOnce(() => '')
jest.spyOn(io, 'which').mockImplementationOnce(async () => '')
await expect(() => getKubectlPath()).rejects.toThrow()
})
})
@@ -52,7 +46,7 @@ describe('Kubectl class', () => {
const mockExecReturn = {exitCode: 0, stdout: 'Output', stderr: ''}
beforeEach(() => {
vi.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
jest.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
return mockExecReturn
})
})
@@ -643,7 +637,7 @@ describe('Kubectl class', () => {
stderr: ''
}
vi.spyOn(exec, 'getExecOutput').mockImplementationOnce(async () => {
jest.spyOn(exec, 'getExecOutput').mockImplementationOnce(async () => {
return describeReturn
})
@@ -656,7 +650,7 @@ describe('Kubectl class', () => {
const skipTls = true
const kubectl = new Kubectl(kubectlPath, testNamespace, skipTls)
vi.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
jest.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
return {exitCode: 0, stderr: '', stdout: ''}
})
@@ -683,7 +677,7 @@ describe('Kubectl namespace handling', () => {
const execReturn = {exitCode: 0, stdout: 'Output', stderr: ''}
beforeEach(() => {
vi.spyOn(exec, 'getExecOutput').mockResolvedValue(execReturn)
jest.spyOn(exec, 'getExecOutput').mockResolvedValue(execReturn)
})
const runApply = async (namespace?: string) => {
+2 -6
View File
@@ -1,5 +1,5 @@
import {ExecOutput, getExecOutput} from '@actions/exec'
import {createInlineArray} from '../utilities/arrayUtils.js'
import {createInlineArray} from '../utilities/arrayUtils'
import * as core from '@actions/core'
import * as toolCache from '@actions/tool-cache'
import * as io from '@actions/io'
@@ -238,17 +238,13 @@ export class Kubectl {
})
}
public getNamespace(namespaceOverride?: string): string {
return namespaceOverride || this.namespace
}
protected getFlags(namespaceOverride?: string): string[] {
const flags = []
if (this.ignoreSSLErrors) {
flags.push('--insecure-skip-tls-verify')
}
const ns = this.getNamespace(namespaceOverride)
const ns = namespaceOverride || this.namespace
if (ns) {
flags.push('--namespace', ns)
}
+1 -1
View File
@@ -10,7 +10,7 @@ import {
ServiceTypes,
WORKLOAD_TYPES,
WORKLOAD_TYPES_WITH_ROLLOUT_STATUS
} from './kubernetesTypes.js'
} from './kubernetesTypes'
describe('Kubernetes types', () => {
it('contains kubernetes workloads', () => {
+6 -9
View File
@@ -1,13 +1,10 @@
import {vi} from 'vitest'
vi.mock('@actions/exec')
import * as fileUtils from '../utilities/fileUtils.js'
import * as fileUtils from '../utilities/fileUtils'
import fs from 'node:fs'
import {
PrivateKubectl,
extractFileNames,
replaceFileNamesWithShallowNamesRelativeToTemp
} from './privatekubectl.js'
} from './privatekubectl'
import * as exec from '@actions/exec'
describe('Private kubectl', () => {
@@ -20,14 +17,14 @@ describe('Private kubectl', () => {
'resourceName'
)
const spy = vi
const spy = jest
.spyOn(fileUtils, 'getTempDirectory')
.mockImplementation(() => {
return '/tmp'
})
vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
vi.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
jest.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
return 'test contents'
})
@@ -51,7 +48,7 @@ describe('Private kubectl', () => {
test('Should throw well defined Error on error from Azure', async () => {
const errorMsg = 'An error message'
vi.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
jest.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
return {exitCode: 1, stdout: '', stderr: errorMsg}
})
+2 -2
View File
@@ -1,10 +1,10 @@
import {Kubectl} from './kubectl.js'
import {Kubectl} from './kubectl'
import minimist from 'minimist'
import {ExecOptions, ExecOutput, getExecOutput} from '@actions/exec'
import * as core from '@actions/core'
import fs from 'node:fs'
import * as path from 'path'
import {getTempDirectory} from '../utilities/fileUtils.js'
import {getTempDirectory} from '../utilities/fileUtils'
export class PrivateKubectl extends Kubectl {
protected async execute(args: string[], silent: boolean = false) {
+1 -1
View File
@@ -1,4 +1,4 @@
import {parseRouteStrategy, RouteStrategy} from './routeStrategy.js'
import {parseRouteStrategy, RouteStrategy} from './routeStrategy'
describe('Route strategy type', () => {
test('it has required values', () => {
+1 -4
View File
@@ -1,7 +1,4 @@
import {
parseTrafficSplitMethod,
TrafficSplitMethod
} from './trafficSplitMethod.js'
import {parseTrafficSplitMethod, TrafficSplitMethod} from './trafficSplitMethod'
describe('Traffic split method type', () => {
test('it has required values', () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import {createInlineArray} from './arrayUtils.js'
import {createInlineArray} from './arrayUtils'
describe('array utilities', () => {
it('creates an inline array', () => {
+3 -6
View File
@@ -1,18 +1,15 @@
import {vi} from 'vitest'
vi.mock('@actions/io')
import * as io from '@actions/io'
import {checkDockerPath} from './dockerUtils.js'
import {checkDockerPath} from './dockerUtils'
describe('docker utilities', () => {
it('checks if docker is installed', async () => {
// docker installed
const path = 'path'
vi.spyOn(io, 'which').mockImplementationOnce(async () => path)
jest.spyOn(io, 'which').mockImplementationOnce(async () => path)
expect(() => checkDockerPath()).not.toThrow()
// docker not installed
vi.spyOn(io, 'which').mockImplementationOnce(async () => {
jest.spyOn(io, 'which').mockImplementationOnce(async () => {
throw new Error('not found')
})
await expect(() => checkDockerPath()).rejects.toThrow()
+3 -3
View File
@@ -1,8 +1,8 @@
import * as io from '@actions/io'
import {DeploymentConfig} from '../types/deploymentConfig.js'
import {DeploymentConfig} from '../types/deploymentConfig'
import * as core from '@actions/core'
import {DockerExec} from '../types/docker.js'
import {getNormalizedPath} from './githubUtils.js'
import {DockerExec} from '../types/docker'
import {getNormalizedPath} from './githubUtils'
export async function getDeploymentConfig(): Promise<DeploymentConfig> {
let helmChartPaths: string[] =
+5 -6
View File
@@ -1,10 +1,9 @@
import {vi, type Mocked} from 'vitest'
import {parseDuration} from './durationUtils.js'
import {parseDuration} from './durationUtils'
import * as core from '@actions/core'
// Mock core.debug
vi.mock('@actions/core')
const mockCore = core as Mocked<typeof core>
jest.mock('@actions/core')
const mockCore = core as jest.Mocked<typeof core>
// Test data constants
const VALID_TIMEOUTS = {
@@ -47,7 +46,7 @@ const expectInvalidTimeout = (input: string, expectedError: string) => {
describe('validateTimeoutDuration', () => {
beforeEach(() => {
vi.clearAllMocks()
jest.clearAllMocks()
})
describe('valid timeout formats', () => {
@@ -91,7 +90,7 @@ describe('validateTimeoutDuration', () => {
'No unit specified for timeout "5", assuming minutes'
)
vi.clearAllMocks()
jest.clearAllMocks()
parseDuration('30s')
expect(mockCore.debug).not.toHaveBeenCalled()
+5 -6
View File
@@ -1,10 +1,9 @@
import {vi} from 'vitest'
import * as fileUtils from './fileUtils.js'
import * as fileUtils from './fileUtils'
import * as yaml from 'js-yaml'
import fs from 'node:fs'
import * as path from 'path'
import {K8sObject} from '../types/k8sObject.js'
import {K8sObject} from '../types/k8sObject'
const sampleYamlUrl =
'https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/controllers/nginx-deployment.yaml'
@@ -70,7 +69,7 @@ describe('File utils', () => {
'manifest_test_dir'
)
await expect(
expect(
fileUtils.getFilesFromDirectoriesAndURLs([badPath, goodPath])
).rejects.toThrow()
})
@@ -109,8 +108,8 @@ describe('File utils', () => {
describe('moving files to temp', () => {
it('correctly moves the contents of a file to the temporary directory', () => {
vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
vi.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
jest.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
return 'test contents'
})
const originalFilePath = path.join('path', 'in', 'repo')
+4 -4
View File
@@ -4,10 +4,10 @@ import * as path from 'path'
import * as core from '@actions/core'
import * as os from 'os'
import * as yaml from 'js-yaml'
import {Errorable, succeeded, failed, Failed} from '../types/errorable.js'
import {getCurrentTime} from './timeUtils.js'
import {isHttpUrl} from './githubUtils.js'
import {K8sObject} from '../types/k8sObject.js'
import {Errorable, succeeded, failed, Failed} from '../types/errorable'
import {getCurrentTime} from './timeUtils'
import {isHttpUrl} from './githubUtils'
import {K8sObject} from '../types/k8sObject'
export const urlFileKind = 'urlfile'
+1 -1
View File
@@ -2,7 +2,7 @@ import {
getNormalizedPath,
isHttpUrl,
normalizeWorkflowStrLabel
} from './githubUtils.js'
} from './githubUtils'
describe('Github utils', () => {
it('normalizes workflow string labels', () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import {GitHubClient, OkStatusCode} from '../types/githubClient.js'
import {GitHubClient, OkStatusCode} from '../types/githubClient'
import * as core from '@actions/core'
export async function getWorkflowFilePath(
+2 -6
View File
@@ -1,9 +1,6 @@
import {vi} from 'vitest'
vi.mock('@actions/core')
import * as core from '@actions/core'
import {ExecOutput} from '@actions/exec'
import {checkForErrors} from './kubectlUtils.js'
import {checkForErrors} from './kubectlUtils'
describe('Kubectl utils', () => {
it('checks for errors', () => {
@@ -42,8 +39,7 @@ describe('Kubectl utils', () => {
).toThrow()
// with warn behavior
const warnSpy = vi.spyOn(core, 'warning').mockImplementation(() => {})
warnSpy.mockClear()
jest.spyOn(core, 'warning').mockImplementation(() => {})
let warningCalls = 0
expect(() => checkForErrors([success], true)).not.toThrow()
expect(core.warning).toHaveBeenCalledTimes(warningCalls)
+1 -1
View File
@@ -1,6 +1,6 @@
import * as core from '@actions/core'
import {ExecOutput} from '@actions/exec'
import {Kubectl} from '../types/kubectl.js'
import {Kubectl} from '../types/kubectl'
const NAMESPACE = 'namespace'
+1 -1
View File
@@ -1,4 +1,4 @@
import {KubernetesWorkload} from '../types/kubernetesTypes.js'
import {KubernetesWorkload} from '../types/kubernetesTypes'
export function getImagePullSecrets(inputObject: any) {
const kind = inputObject?.kind?.toLowerCase()
+1 -1
View File
@@ -3,7 +3,7 @@ import {
isServiceEntity,
KubernetesWorkload,
NullInputObjectError
} from '../types/kubernetesTypes.js'
} from '../types/kubernetesTypes'
export function updateSpecLabels(
inputObject: any,
+45 -106
View File
@@ -1,29 +1,10 @@
import {vi} from 'vitest'
import type {MockInstance} from 'vitest'
vi.mock('@actions/core', async (importOriginal) => {
const actual: any = await importOriginal()
return {
...actual,
getInput: vi.fn().mockReturnValue(''),
debug: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
setFailed: vi.fn(),
setOutput: vi.fn()
}
})
import * as manifestStabilityUtils from './manifestStabilityUtils.js'
import {Kubectl} from '../types/kubectl.js'
import {
ResourceTypeFleet,
ResourceTypeManagedCluster
} from '../actions/deploy.js'
import * as manifestStabilityUtils from './manifestStabilityUtils'
import {Kubectl} from '../types/kubectl'
import {ResourceTypeFleet, ResourceTypeManagedCluster} from '../actions/deploy'
import {ExecOutput} from '@actions/exec'
import {exitCode, stdout} from 'process'
import * as core from '@actions/core'
import * as timeUtils from './timeUtils.js'
import * as timeUtils from './timeUtils'
describe('manifestStabilityUtils', () => {
const kc = new Kubectl('')
@@ -36,8 +17,8 @@ describe('manifestStabilityUtils', () => {
]
it('should return immediately if the resource type is fleet', async () => {
const spy = vi.spyOn(manifestStabilityUtils, 'checkManifestStability')
const checkRolloutStatusSpy = vi.spyOn(kc, 'checkRolloutStatus')
const spy = jest.spyOn(manifestStabilityUtils, 'checkManifestStability')
const checkRolloutStatusSpy = jest.spyOn(kc, 'checkRolloutStatus')
await manifestStabilityUtils.checkManifestStability(
kc,
resources,
@@ -49,8 +30,8 @@ describe('manifestStabilityUtils', () => {
})
it('should run fully if the resource type is managedCluster', async () => {
const spy = vi.spyOn(manifestStabilityUtils, 'checkManifestStability')
const checkRolloutStatusSpy = vi
const spy = jest.spyOn(manifestStabilityUtils, 'checkManifestStability')
const checkRolloutStatusSpy = jest
.spyOn(kc, 'checkRolloutStatus')
.mockImplementation(() => {
return new Promise<ExecOutput>((resolve, reject) => {
@@ -73,7 +54,7 @@ describe('manifestStabilityUtils', () => {
it('should pass timeout to checkRolloutStatus when provided', async () => {
const timeout = '300s'
const checkRolloutStatusSpy = vi
const checkRolloutStatusSpy = jest
.spyOn(kc, 'checkRolloutStatus')
.mockImplementation(() => {
return new Promise<ExecOutput>((resolve, reject) => {
@@ -101,7 +82,7 @@ describe('manifestStabilityUtils', () => {
})
it('should call checkRolloutStatus without timeout when not provided', async () => {
const checkRolloutStatusSpy = vi
const checkRolloutStatusSpy = jest
.spyOn(kc, 'checkRolloutStatus')
.mockImplementation(() => {
return new Promise<ExecOutput>((resolve, reject) => {
@@ -130,76 +111,34 @@ describe('manifestStabilityUtils', () => {
describe('checkManifestStability failure and resource-specific scenarios', () => {
let kc: Kubectl
let coreErrorSpy: MockInstance
let coreInfoSpy: MockInstance
let coreWarningSpy: MockInstance
let coreErrorSpy: jest.SpyInstance
let coreInfoSpy: jest.SpyInstance
let coreWarningSpy: jest.SpyInstance
beforeEach(() => {
kc = new Kubectl('', 'default')
coreErrorSpy = vi.spyOn(core, 'error').mockImplementation(() => {})
coreInfoSpy = vi.spyOn(core, 'info').mockImplementation(() => {})
coreWarningSpy = vi.spyOn(core, 'warning').mockImplementation(() => {})
kc = new Kubectl('')
coreErrorSpy = jest.spyOn(core, 'error').mockImplementation()
coreInfoSpy = jest.spyOn(core, 'info').mockImplementation()
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation()
})
afterEach(() => {
vi.restoreAllMocks()
jest.restoreAllMocks()
})
it('should call describe and collect errors when a rollout fails', async () => {
const resources = [
{type: 'deployment', name: 'failing-app', namespace: 'app-ns-123'}
{type: 'deployment', name: 'failing-app', namespace: 'default'}
]
const rolloutError = new Error('Progress deadline exceeded')
const describeOutput =
'Events:\n Type\tReason\tMessage\n Normal\tScalingReplicaSet\tScaled up replica set failing-app-123 to 1'
// Arrange: Mock rollout to fail and describe to succeed
const checkRolloutStatusSpy = vi
const checkRolloutStatusSpy = jest
.spyOn(kc, 'checkRolloutStatus')
.mockRejectedValue(rolloutError)
const describeSpy = vi.spyOn(kc, 'describe').mockResolvedValue({
stdout: describeOutput,
stderr: '',
exitCode: 0
})
// Act & Assert: Expect the function to throw the final aggregated error
const expectedErrorMessage = `Rollout failed for deployment/failing-app in namespace app-ns-123: ${rolloutError.message}`
await expect(
manifestStabilityUtils.checkManifestStability(
kc,
resources,
ResourceTypeManagedCluster
)
).rejects.toThrow(
`Rollout status failed for the following resources:\n${expectedErrorMessage}`
)
// Assert that the correct functions were called
expect(checkRolloutStatusSpy).toHaveBeenCalledTimes(1)
expect(coreErrorSpy).toHaveBeenCalledWith(expectedErrorMessage)
expect(describeSpy).toHaveBeenCalledWith(
'deployment',
'failing-app',
false,
'app-ns-123'
)
expect(coreInfoSpy).toHaveBeenCalledWith(
`Describe output for deployment/failing-app:\n${describeOutput}`
)
})
it('should use the default kubectl namespace when none is provided', async () => {
const resources = [{type: 'deployment', name: 'failing-app'}]
const rolloutError = new Error('Progress deadline exceeded')
const describeOutput =
'Events:\n Type\tReason\tMessage\n Normal\tScalingReplicaSet\tScaled up replica set failing-app-123 to 1'
// Arrange: Mock rollout to fail and describe to succeed
const checkRolloutStatusSpy = vi
.spyOn(kc, 'checkRolloutStatus')
.mockRejectedValue(rolloutError)
const describeSpy = vi.spyOn(kc, 'describe').mockResolvedValue({
const describeSpy = jest.spyOn(kc, 'describe').mockResolvedValue({
stdout: describeOutput,
stderr: '',
exitCode: 0
@@ -224,7 +163,7 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
'deployment',
'failing-app',
false,
undefined
'default'
)
expect(coreInfoSpy).toHaveBeenCalledWith(
`Describe output for deployment/failing-app:\n${describeOutput}`
@@ -235,10 +174,10 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
const resources = [{type: 'Pod', name: 'test-pod', namespace: 'default'}]
// Arrange: Spy on checkPodStatus and checkRolloutStatus
const checkPodStatusSpy = vi
const checkPodStatusSpy = jest
.spyOn(manifestStabilityUtils, 'checkPodStatus')
.mockResolvedValue() // Assume pod becomes ready
const checkRolloutStatusSpy = vi.spyOn(kc, 'checkRolloutStatus')
const checkRolloutStatusSpy = jest.spyOn(kc, 'checkRolloutStatus')
// Act
await manifestStabilityUtils.checkManifestStability(
@@ -259,10 +198,10 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
const podError = new Error('Pod rollout failed')
// Arrange: Mock checkPodStatus to fail
const checkPodStatusSpy = vi
const checkPodStatusSpy = jest
.spyOn(manifestStabilityUtils, 'checkPodStatus')
.mockRejectedValue(podError)
const describeSpy = vi.spyOn(kc, 'describe').mockResolvedValue({
const describeSpy = jest.spyOn(kc, 'describe').mockResolvedValue({
stdout: 'describe output',
stderr: '',
exitCode: 0
@@ -290,7 +229,7 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
it('should wait for external IP for a LoadBalancer service', async () => {
//Spying on sleep to avoid actual delays in tests
vi.spyOn(timeUtils, 'sleep').mockResolvedValue(undefined)
jest.spyOn(timeUtils, 'sleep').mockResolvedValue(undefined)
const resources = [
{type: 'service', name: 'test-svc', namespace: 'default'}
]
@@ -304,7 +243,7 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
}
// Arrange: Mock getResource to simulate the IP being assigned on the second poll
const getResourceSpy = vi
const getResourceSpy = jest
.spyOn(kc, 'getResource')
// First call: Initial service check
.mockResolvedValueOnce({
@@ -347,10 +286,10 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
// Arrange: Mock getService to fail, and describe to succeed
// Note: We mock getResource because getService is a private helper
const getResourceSpy = vi
const getResourceSpy = jest
.spyOn(kc, 'getResource')
.mockRejectedValue(getServiceError)
const describeSpy = vi.spyOn(kc, 'describe').mockResolvedValue({
const describeSpy = jest.spyOn(kc, 'describe').mockResolvedValue({
stdout: 'describe output',
stderr: '',
exitCode: 0
@@ -388,7 +327,7 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
}
// Arrange
const getResourceSpy = vi.spyOn(kc, 'getResource').mockResolvedValue({
const getResourceSpy = jest.spyOn(kc, 'getResource').mockResolvedValue({
stdout: JSON.stringify(clusterIpService),
stderr: '',
exitCode: 0
@@ -411,19 +350,19 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
describe('checkManifestStability additional scenarios', () => {
let kc: Kubectl
let coreErrorSpy: MockInstance
let coreInfoSpy: MockInstance
let coreWarningSpy: MockInstance
let coreErrorSpy: jest.SpyInstance
let coreInfoSpy: jest.SpyInstance
let coreWarningSpy: jest.SpyInstance
beforeEach(() => {
kc = new Kubectl('')
coreErrorSpy = vi.spyOn(core, 'error').mockImplementation(() => {})
coreInfoSpy = vi.spyOn(core, 'info').mockImplementation(() => {})
coreWarningSpy = vi.spyOn(core, 'warning').mockImplementation(() => {})
coreErrorSpy = jest.spyOn(core, 'error').mockImplementation()
coreInfoSpy = jest.spyOn(core, 'info').mockImplementation()
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation()
})
afterEach(() => {
vi.restoreAllMocks()
jest.restoreAllMocks()
})
it('should aggregate errors from deployment and pod failures', async () => {
@@ -435,15 +374,15 @@ describe('checkManifestStability additional scenarios', () => {
const podError = new Error('Pod not ready in time')
// Arrange: Mock failures
const checkRolloutStatusSpy = vi
const checkRolloutStatusSpy = jest
.spyOn(kc, 'checkRolloutStatus')
.mockRejectedValue(deploymentError)
// For pod: simulate a pod check failure
const checkPodStatusSpy = vi
const checkPodStatusSpy = jest
.spyOn(manifestStabilityUtils, 'checkPodStatus')
.mockRejectedValue(podError)
// For both, simulate a successful describe call to provide additional details
const describeSpy = vi.spyOn(kc, 'describe').mockResolvedValue({
const describeSpy = jest.spyOn(kc, 'describe').mockResolvedValue({
stdout: 'describe aggregated output',
stderr: '',
exitCode: 0
@@ -482,25 +421,25 @@ describe('checkManifestStability additional scenarios', () => {
// Arrange:
// Deployment rollout succeeds
vi.spyOn(kc, 'checkRolloutStatus').mockResolvedValue({
jest.spyOn(kc, 'checkRolloutStatus').mockResolvedValue({
exitCode: 0,
stderr: '',
stdout: ''
})
// Pod becomes ready
vi.spyOn(manifestStabilityUtils, 'checkPodStatus').mockResolvedValue()
jest.spyOn(manifestStabilityUtils, 'checkPodStatus').mockResolvedValue()
// Simulate a LoadBalancer service that already has an external IP
const stableService = {
spec: {type: 'LoadBalancer'},
status: {loadBalancer: {ingress: [{ip: '1.2.3.4'}]}}
}
vi.spyOn(kc, 'getResource').mockResolvedValue({
jest.spyOn(kc, 'getResource').mockResolvedValue({
stdout: JSON.stringify(stableService),
stderr: '',
exitCode: 0
})
// Provide a describe result to avoid warnings
vi.spyOn(kc, 'describe').mockResolvedValue({
jest.spyOn(kc, 'describe').mockResolvedValue({
stdout: 'describe output stable',
stderr: '',
exitCode: 0
+8 -8
View File
@@ -1,10 +1,10 @@
import * as core from '@actions/core'
import * as KubernetesConstants from '../types/kubernetesTypes.js'
import {Kubectl, Resource} from '../types/kubectl.js'
import {checkForErrors} from './kubectlUtils.js'
import {sleep} from './timeUtils.js'
import {ResourceTypeFleet} from '../actions/deploy.js'
import {ClusterType} from '../inputUtils.js'
import * as KubernetesConstants from '../types/kubernetesTypes'
import {Kubectl, Resource} from '../types/kubectl'
import {checkForErrors} from './kubectlUtils'
import {sleep} from './timeUtils'
import {ResourceTypeFleet} from '../actions/deploy'
import {ClusterType} from '../inputUtils'
const IS_SILENT = false
const POD = 'pod'
@@ -46,7 +46,7 @@ export async function checkManifestStability(
)
checkForErrors([result])
} catch (ex) {
const errorMessage = `Rollout failed for ${resource.type}/${resource.name} in namespace ${kubectl.getNamespace(resource.namespace)}: ${ex.message || ex}`
const errorMessage = `Rollout failed for ${resource.type}/${resource.name} in namespace ${resource.namespace}: ${ex.message || ex}`
core.error(errorMessage)
rolloutErrors.push(errorMessage)
@@ -106,7 +106,7 @@ export async function checkManifestStability(
}
}
} catch (ex) {
const errorMessage = `Could not determine service status of: ${resource.name} in namespace ${kubectl.getNamespace(resource.namespace)}. Error: ${ex.message || ex}`
const errorMessage = `Could not determine service status of: ${resource.name} in namespace ${resource.namespace}. Error: ${ex.message || ex}`
core.warning(errorMessage)
try {
+5 -8
View File
@@ -1,17 +1,14 @@
import {vi} from 'vitest'
vi.mock('fs')
import * as fileUtils from './fileUtils.js'
import * as manifestUpdateUtils from './manifestUpdateUtils.js'
import * as fileUtils from './fileUtils'
import * as manifestUpdateUtils from './manifestUpdateUtils'
import * as path from 'path'
import * as fs from 'fs'
describe('manifestUpdateUtils', () => {
vi.spyOn(fileUtils, 'moveFileToTmpDir').mockImplementation((filename) => {
jest.spyOn(fileUtils, 'moveFileToTmpDir').mockImplementation((filename) => {
return path.join('/tmp', filename)
})
vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
vi.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
jest.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
return 'test contents'
})
+7 -7
View File
@@ -2,25 +2,25 @@ import * as core from '@actions/core'
import * as fs from 'fs'
import * as yaml from 'js-yaml'
import * as path from 'path'
import * as fileHelper from './fileUtils.js'
import {moveFileToTmpDir} from './fileUtils.js'
import * as fileHelper from './fileUtils'
import {moveFileToTmpDir} from './fileUtils'
import {
InputObjectKindNotDefinedError,
InputObjectMetadataNotDefinedError,
isWorkloadEntity,
KubernetesWorkload,
NullInputObjectError
} from '../types/kubernetesTypes.js'
} from '../types/kubernetesTypes'
import {
getSpecSelectorLabels,
setSpecSelectorLabels
} from './manifestSpecLabelUtils.js'
} from './manifestSpecLabelUtils'
import {
getImagePullSecrets,
setImagePullSecrets
} from './manifestPullSecretUtils.js'
import {Resource} from '../types/kubectl.js'
import {K8sObject} from '../types/k8sObject.js'
} from './manifestPullSecretUtils'
import {Resource} from '../types/kubectl'
import {K8sObject} from '../types/k8sObject'
export function updateManifestFiles(manifestFilePaths: string[]) {
if (manifestFilePaths?.length === 0) {
+5 -4
View File
@@ -1,12 +1,11 @@
import {
getImagePullSecrets,
setImagePullSecrets
} from './manifestPullSecretUtils.js'
import {updateSpecLabels} from './manifestSpecLabelUtils.js'
import {getReplicaCount} from './manifestUpdateUtils.js'
} from './manifestPullSecretUtils'
import {updateSpecLabels} from './manifestSpecLabelUtils'
import {getReplicaCount} from './manifestUpdateUtils'
import * as yaml from 'js-yaml'
import * as fs from 'fs'
import {isWorkloadEntity, isDeploymentEntity} from '../types/kubernetesTypes.js'
describe('ScaledJob Support', () => {
let scaledJobObject: any
@@ -58,11 +57,13 @@ describe('ScaledJob Support', () => {
describe('Workload Classification', () => {
it('should classify ScaledJob as workload entity', () => {
const {isWorkloadEntity} = require('../types/kubernetesTypes')
expect(isWorkloadEntity('ScaledJob')).toBe(true)
expect(isWorkloadEntity('scaledjob')).toBe(true)
})
it('should not classify ScaledJob as deployment entity', () => {
const {isDeploymentEntity} = require('../types/kubernetesTypes')
expect(isDeploymentEntity('scaledjob')).toBe(false)
expect(isDeploymentEntity('ScaledJob')).toBe(false)
})
+1 -1
View File
@@ -1,4 +1,4 @@
import {Kubectl} from '../types/kubectl.js'
import {Kubectl} from '../types/kubectl'
const trafficSplitAPIVersionPrefix = 'split.smi-spec.io'
@@ -2,7 +2,7 @@ import {
cleanLabel,
removeInvalidLabelCharacters,
VALID_LABEL_REGEX
} from '../utilities/workflowAnnotationUtils.js'
} from '../utilities/workflowAnnotationUtils'
describe('WorkflowAnnotationUtils', () => {
describe('cleanLabel', () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import {DeploymentConfig} from '../types/deploymentConfig.js'
import {DeploymentConfig} from '../types/deploymentConfig'
const ANNOTATION_PREFIX = 'actions.github.com'
+4 -11
View File
@@ -1,15 +1,8 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"types": ["vitest/globals"],
"esModuleInterop": true,
"strict": false,
"skipLibCheck": true,
"resolveJsonModule": true,
"declaration": false,
"sourceMap": true
"target": "ES6",
"module": "commonjs",
"esModuleInterop": true
},
"exclude": ["node_modules", "test", "lib"]
"exclude": ["node_modules", "test", "src/**/*.test.ts"]
}
-11
View File
@@ -1,11 +0,0 @@
import {defineConfig} from 'vitest/config'
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['**/*.test.ts'],
testTimeout: 9000,
clearMocks: true
}
})