Compare commits

..

1 Commits

Author SHA1 Message Date
GitHub Action 6f7c489cec build 2025-08-06 17:20:35 +00:00
85 changed files with 28807 additions and 2945 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: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #v6.0.3 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with: with:
# We must fetch at least the immediate parents so that if this is # We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head. # a pull request then we can checkout the head.
@@ -24,7 +24,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e #v3.29.5 uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d #v3.29.5
# Override language selection by uncommenting this and choosing your languages # Override language selection by uncommenting this and choosing your languages
# with: # with:
# languages: go, javascript, csharp, python, cpp, java # languages: go, javascript, csharp, python, cpp, java
@@ -32,7 +32,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e #v3.29.5 uses: github/codeql-action/autobuild@51f77329afa6477de8c49fc9c7046c15b9a4e79d #v3.29.5
# ️ Command-line programs to run using the OS shell. # ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -46,4 +46,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e #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 represent a sequence of tasks that will be executed as part of the job
steps: steps:
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
name: Setting issue as idle name: Setting issue as idle
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -24,7 +24,7 @@ jobs:
operations-per-run: 100 operations-per-run: 100
exempt-issue-labels: 'backlog' exempt-issue-labels: 'backlog'
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
name: Setting PR as idle name: Setting PR as idle
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: install deps - name: install deps
run: npm install run: npm install
+1 -1
View File
@@ -13,6 +13,6 @@ jobs:
permissions: permissions:
actions: read actions: read
contents: write contents: write
uses: Azure/action-release-workflows/.github/workflows/release_js_project.yaml@3c677ba5ab58f5c5c1a6f0cfb176b333b1f27405 # v1 uses: Azure/action-release-workflows/.github/workflows/release_js_project.yaml@v1
with: with:
changelogPath: ./CHANGELOG.md changelogPath: ./CHANGELOG.md
@@ -18,15 +18,39 @@ jobs:
KUBECONFIG: /home/runner/.kube/config KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }} NAMESPACE: test-${{ github.run_id }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup - name: Install dependencies
name: Setup Minikube Environment run: |
timeout-minutes: 5 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 - name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }} 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 - name: Cleaning any previously created items
run: | run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }} python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
@@ -12,21 +12,45 @@ on:
jobs: jobs:
run-integration-test: run-integration-test:
name: Blue-Green Ingress Tests name: Run Minikube Integration Tests
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
KUBECONFIG: /home/runner/.kube/config KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }} NAMESPACE: test-${{ github.run_id }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup - name: Install dependencies
name: Setup Minikube Environment run: |
timeout-minutes: 5 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 - name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }} 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 - name: Cleaning any previously created items
run: | run: |
python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service' ${{ env.NAMESPACE }} python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service' ${{ env.NAMESPACE }}
@@ -12,21 +12,45 @@ on:
jobs: jobs:
run-integration-test: run-integration-test:
name: Blue-Green Service Tests name: Run Minikube Integration Tests
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
KUBECONFIG: /home/runner/.kube/config KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }} NAMESPACE: test-${{ github.run_id }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup - name: Install dependencies
name: Setup Minikube Environment run: |
timeout-minutes: 5 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 - name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }} 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 - name: Cleaning any previously created items
run: | run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }} python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
@@ -12,23 +12,56 @@ on:
jobs: jobs:
run-integration-test: run-integration-test:
name: Blue-Green SMI Tests name: Run Minikube Integration Tests
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
KUBECONFIG: /home/runner/.kube/config KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }} NAMESPACE: test-${{ github.run_id }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup - name: Install dependencies
name: Setup Minikube Environment run: |
timeout-minutes: 5 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: 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 - name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }} 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 - name: Cleaning any previously created items
run: | run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }} python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
@@ -12,21 +12,45 @@ on:
jobs: jobs:
run-integration-test: run-integration-test:
name: Canary Pod Tests name: Run Minikube Integration Tests
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
KUBECONFIG: /home/runner/.kube/config KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }} NAMESPACE: test-${{ github.run_id }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup - name: Install dependencies
name: Setup Minikube Environment run: |
timeout-minutes: 5 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 - name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }} 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 - name: Cleaning any previously created items
run: | run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }} python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
@@ -12,23 +12,56 @@ on:
jobs: jobs:
run-integration-test: run-integration-test:
name: Canary SMI Tests name: Run Minikube Integration Tests
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
KUBECONFIG: /home/runner/.kube/config KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }} NAMESPACE: test-${{ github.run_id }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup - name: Install dependencies
name: Setup Minikube Environment run: |
timeout-minutes: 5 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: 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 - name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }} 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 - name: Cleaning any previously created items
run: | run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }} python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
@@ -14,18 +14,37 @@ on:
jobs: jobs:
run-integration-test: run-integration-test:
name: Namespace Optional Tests name: Run Namespace Optional Integration Tests
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
KUBECONFIG: /home/runner/.kube/config KUBECONFIG: /home/runner/.kube/config
NAMESPACE1: integration-test-namespace1-${{ github.run_id }} NAMESPACE1: integration-test-namespace1-${{ github.run_id }}
NAMESPACE2: integration-test-namespace2-${{ github.run_id }} NAMESPACE2: integration-test-namespace2-${{ github.run_id }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup - name: Install dependencies
name: Setup Minikube Environment run: |
timeout-minutes: 5 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 - name: Create namespaces for tests
run: | run: |
@@ -33,6 +52,11 @@ jobs:
kubectl create ns ${{ env.NAMESPACE2 }} kubectl create ns ${{ env.NAMESPACE2 }}
kubectl create ns test-namespace 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 - name: Cleaning any previously created items
run: | run: |
python test/integration/k8s-deploy-delete.py 'Deployment' 'all' ${{ env.NAMESPACE1 }} python test/integration/k8s-deploy-delete.py 'Deployment' 'all' ${{ env.NAMESPACE1 }}
@@ -19,24 +19,24 @@ jobs:
contents: read contents: read
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install dependencies - name: Install dependencies
run: | run: |
rm -rf node_modules/ rm -rf node_modules/
npm install npm install
- name: Install ncc
run: npm i -g @vercel/ncc
- name: Build - name: Build
run: npm run build run: ncc build src/run.ts -o lib
- name: Azure login - name: Azure login
uses: azure/login@v3.0.0 uses: azure/login@v2.3.0
with: with:
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- uses: Azure/setup-kubectl@829323503d1be3d00ca8346e5391ca0b07a9ab0d # v5.1.0 - uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
name: Install Kubectl name: Install Kubectl
- name: Create private AKS cluster and set context - name: Create private AKS cluster and set context
@@ -51,7 +51,7 @@ jobs:
run: | run: |
az aks command invoke --resource-group ${{ env.NAMESPACE }} --name ${{ env.NAMESPACE }} --command "kubectl create ns ${{ env.NAMESPACE }}" 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 name: Install Python
with: with:
python-version: '3.x' python-version: '3.x'
@@ -12,21 +12,45 @@ on:
jobs: jobs:
run-integration-test: run-integration-test:
name: Resource Annotation Tests name: Run Minikube Integration Tests
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
KUBECONFIG: /home/runner/.kube/config KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }} NAMESPACE: test-${{ github.run_id }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/minikube-setup - name: Install dependencies
name: Setup Minikube Environment run: |
timeout-minutes: 5 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 - name: Create namespace to run tests
run: kubectl create ns ${{ env.NAMESPACE }} 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 - name: Cleaning any previously created items
run: | run: |
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }} python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
name: Run Unit Tests name: Run Unit Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: | - run: |
npm install npm install
npm test npm test
-1
View File
@@ -2,6 +2,5 @@ node_modules
.DS_Store .DS_Store
.idea .idea
lib/
coverage/ coverage/
-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 test
npm run format-check || { npm run format-check || {
echo "" echo ""
-26
View File
@@ -1,31 +1,5 @@
# Changelog # 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 ## [5.0.4] - 2025-08-05
### Added ### Added
+13 -13
View File
@@ -1,6 +1,6 @@
# Deploy manifests action for Kubernetes # 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. 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: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: Azure/docker-login@v2 - uses: Azure/docker-login@v1
with: with:
login-server: contoso.azurecr.io login-server: contoso.azurecr.io
username: ${{ secrets.REGISTRY_USERNAME }} username: ${{ secrets.REGISTRY_USERNAME }}
@@ -331,7 +331,7 @@ jobs:
cluster-name: contoso cluster-name: contoso
resource-group: contoso-rg resource-group: contoso-rg
- uses: Azure/k8s-create-secret@v5 - uses: Azure/k8s-create-secret@v4
with: with:
container-registry-url: contoso.azurecr.io container-registry-url: contoso.azurecr.io
container-registry-username: ${{ secrets.REGISTRY_USERNAME }} container-registry-username: ${{ secrets.REGISTRY_USERNAME }}
@@ -359,9 +359,9 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: Azure/docker-login@v2 - uses: Azure/docker-login@v1
with: with:
login-server: contoso.azurecr.io login-server: contoso.azurecr.io
username: ${{ secrets.REGISTRY_USERNAME }} username: ${{ secrets.REGISTRY_USERNAME }}
@@ -377,14 +377,14 @@ jobs:
with: with:
kubeconfig: ${{ secrets.KUBE_CONFIG }} kubeconfig: ${{ secrets.KUBE_CONFIG }}
- uses: Azure/k8s-create-secret@v5 - uses: Azure/k8s-create-secret@v4
with: with:
container-registry-url: contoso.azurecr.io container-registry-url: contoso.azurecr.io
container-registry-username: ${{ secrets.REGISTRY_USERNAME }} container-registry-username: ${{ secrets.REGISTRY_USERNAME }}
container-registry-password: ${{ secrets.REGISTRY_PASSWORD }} container-registry-password: ${{ secrets.REGISTRY_PASSWORD }}
secret-name: demo-k8s-secret secret-name: demo-k8s-secret
- uses: Azure/k8s-deploy@v5 - uses: Azure/k8s-deploy@v4
with: with:
action: deploy action: deploy
manifests: | manifests: |
@@ -409,9 +409,9 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: Azure/docker-login@v2 - uses: Azure/docker-login@v1
with: with:
login-server: contoso.azurecr.io login-server: contoso.azurecr.io
username: ${{ secrets.REGISTRY_USERNAME }} username: ${{ secrets.REGISTRY_USERNAME }}
@@ -433,9 +433,9 @@ jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@master
- uses: Azure/docker-login@v2 - uses: Azure/docker-login@v1
with: with:
login-server: contoso.azurecr.io login-server: contoso.azurecr.io
username: ${{ secrets.REGISTRY_USERNAME }} username: ${{ secrets.REGISTRY_USERNAME }}
@@ -450,7 +450,7 @@ jobs:
cluster-name: contoso cluster-name: contoso
resource-group: contoso-rg resource-group: contoso-rg
- uses: Azure/k8s-create-secret@v5 - uses: Azure/k8s-create-secret@v4
with: with:
namespace: ${{ env.NAMESPACE }} namespace: ${{ env.NAMESPACE }}
container-registry-url: contoso.azurecr.io container-registry-url: contoso.azurecr.io
+1 -1
View File
@@ -96,5 +96,5 @@ inputs:
branding: branding:
color: 'green' color: 'green'
runs: runs:
using: 'node24' using: 'node20'
main: 'lib/index.js' 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
}
+18240
View File
File diff suppressed because it is too large Load Diff
+9609 -1583
View File
File diff suppressed because it is too large Load Diff
+23 -19
View File
@@ -1,36 +1,40 @@
{ {
"name": "k8s-deploy-action", "name": "k8s-deploy-action",
"version": "6.0.0", "version": "5.0.0",
"author": "Deepak Sattiraju", "author": "Deepak Sattiraju",
"license": "MIT", "license": "MIT",
"type": "module",
"scripts": { "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);\"", "prebuild": "npm i @vercel/ncc",
"typecheck": "tsc --noEmit", "build": "ncc build src/run.ts -o lib",
"test": "vitest run", "test": "jest",
"coverage": "vitest run --coverage", "coverage": "jest --coverage=true",
"format": "prettier --write .", "format": "prettier --write .",
"format-check": "prettier --check .", "format-check": "prettier --check .",
"prepare": "husky" "prepare": "husky"
}, },
"dependencies": { "dependencies": {
"@actions/core": "^3.0.1", "@actions/core": "^1.11.1",
"@actions/exec": "^3.0.0", "@actions/exec": "^1.0.0",
"@actions/io": "^3.0.2", "@actions/io": "^1.1.3",
"@actions/tool-cache": "4.0.0", "@actions/tool-cache": "2.0.2",
"@octokit/core": "^7.0.6", "@babel/preset-env": "^7.28.0",
"@octokit/plugin-retry": "^8.1.0", "@babel/preset-typescript": "^7.27.1",
"js-yaml": "4.2.0", "@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" "minimist": "^1.2.8"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^30.0.0",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
"@types/node": "^25.9.3", "@types/node": "^24.2.0",
"esbuild": "^0.28", "@vercel/ncc": "^0.38.3",
"husky": "^9.1.7", "jest": "^30.0.5",
"prettier": "^3.8.4", "prettier": "^3.6.2",
"typescript": "6.0.3", "ts-jest": "^29.4.1",
"vitest": "^4" "typescript": "5.9.2"
} }
} }
+8 -8
View File
@@ -1,19 +1,19 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as models from '../types/kubernetesTypes.js' import * as models from '../types/kubernetesTypes'
import * as KubernetesConstants from '../types/kubernetesTypes.js' import * as KubernetesConstants from '../types/kubernetesTypes'
import {Kubectl, Resource} from '../types/kubectl.js' import {Kubectl, Resource} from '../types/kubectl'
import { import {
getResources, getResources,
updateManifestFiles updateManifestFiles
} from '../utilities/manifestUpdateUtils.js' } from '../utilities/manifestUpdateUtils'
import { import {
annotateAndLabelResources, annotateAndLabelResources,
checkManifestStability, checkManifestStability,
deployManifests deployManifests
} from '../strategyHelpers/deploymentHelper.js' } from '../strategyHelpers/deploymentHelper'
import {DeploymentStrategy} from '../types/deploymentStrategy.js' import {DeploymentStrategy} from '../types/deploymentStrategy'
import {parseTrafficSplitMethod} from '../types/trafficSplitMethod.js' import {parseTrafficSplitMethod} from '../types/trafficSplitMethod'
import {ClusterType} from '../inputUtils.js' import {ClusterType} from '../inputUtils'
export const ResourceTypeManagedCluster = export const ResourceTypeManagedCluster =
'Microsoft.ContainerService/managedClusters' 'Microsoft.ContainerService/managedClusters'
export const ResourceTypeFleet = 'Microsoft.ContainerService/fleets' export const ResourceTypeFleet = 'Microsoft.ContainerService/fleets'
+18 -18
View File
@@ -1,44 +1,44 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as canaryDeploymentHelper from '../strategyHelpers/canary/canaryHelper.js' import * as canaryDeploymentHelper from '../strategyHelpers/canary/canaryHelper'
import * as SMICanaryDeploymentHelper from '../strategyHelpers/canary/smiCanaryHelper.js' import * as SMICanaryDeploymentHelper from '../strategyHelpers/canary/smiCanaryHelper'
import * as PodCanaryHelper from '../strategyHelpers/canary/podCanaryHelper.js' import * as PodCanaryHelper from '../strategyHelpers/canary/podCanaryHelper'
import { import {
getResources, getResources,
updateManifestFiles updateManifestFiles
} from '../utilities/manifestUpdateUtils.js' } from '../utilities/manifestUpdateUtils'
import {annotateAndLabelResources} from '../strategyHelpers/deploymentHelper.js' import {annotateAndLabelResources} from '../strategyHelpers/deploymentHelper'
import * as models from '../types/kubernetesTypes.js' import * as models from '../types/kubernetesTypes'
import * as KubernetesManifestUtility from '../utilities/manifestStabilityUtils.js' import * as KubernetesManifestUtility from '../utilities/manifestStabilityUtils'
import { import {
deleteGreenObjects, deleteGreenObjects,
getManifestObjects, getManifestObjects,
NONE_LABEL_VALUE NONE_LABEL_VALUE
} from '../strategyHelpers/blueGreen/blueGreenHelper.js' } from '../strategyHelpers/blueGreen/blueGreenHelper'
import {BlueGreenManifests} from '../types/blueGreenTypes.js' import {BlueGreenManifests} from '../types/blueGreenTypes'
import {DeployResult} from '../types/deployResult.js' import {DeployResult} from '../types/deployResult'
import { import {
promoteBlueGreenIngress, promoteBlueGreenIngress,
promoteBlueGreenService, promoteBlueGreenService,
promoteBlueGreenSMI promoteBlueGreenSMI
} from '../strategyHelpers/blueGreen/promote.js' } from '../strategyHelpers/blueGreen/promote'
import { import {
routeBlueGreenService, routeBlueGreenService,
routeBlueGreenIngressUnchanged, routeBlueGreenIngressUnchanged,
routeBlueGreenSMI routeBlueGreenSMI
} from '../strategyHelpers/blueGreen/route.js' } from '../strategyHelpers/blueGreen/route'
import {cleanupSMI} from '../strategyHelpers/blueGreen/smiBlueGreenHelper.js' import {cleanupSMI} from '../strategyHelpers/blueGreen/smiBlueGreenHelper'
import {Kubectl, Resource} from '../types/kubectl.js' import {Kubectl, Resource} from '../types/kubectl'
import {DeploymentStrategy} from '../types/deploymentStrategy.js' import {DeploymentStrategy} from '../types/deploymentStrategy'
import { import {
parseTrafficSplitMethod, parseTrafficSplitMethod,
TrafficSplitMethod TrafficSplitMethod
} from '../types/trafficSplitMethod.js' } from '../types/trafficSplitMethod'
import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy.js' import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy'
import {ClusterType} from '../inputUtils.js' import {ClusterType} from '../inputUtils'
export async function promote( export async function promote(
kubectl: Kubectl, kubectl: Kubectl,
+9 -9
View File
@@ -1,20 +1,20 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as canaryDeploymentHelper from '../strategyHelpers/canary/canaryHelper.js' import * as canaryDeploymentHelper from '../strategyHelpers/canary/canaryHelper'
import * as SMICanaryDeploymentHelper from '../strategyHelpers/canary/smiCanaryHelper.js' import * as SMICanaryDeploymentHelper from '../strategyHelpers/canary/smiCanaryHelper'
import {Kubectl} from '../types/kubectl.js' import {Kubectl} from '../types/kubectl'
import {BlueGreenManifests} from '../types/blueGreenTypes.js' import {BlueGreenManifests} from '../types/blueGreenTypes'
import { import {
rejectBlueGreenIngress, rejectBlueGreenIngress,
rejectBlueGreenService, rejectBlueGreenService,
rejectBlueGreenSMI rejectBlueGreenSMI
} from '../strategyHelpers/blueGreen/reject.js' } from '../strategyHelpers/blueGreen/reject'
import {getManifestObjects} from '../strategyHelpers/blueGreen/blueGreenHelper.js' import {getManifestObjects} from '../strategyHelpers/blueGreen/blueGreenHelper'
import {DeploymentStrategy} from '../types/deploymentStrategy.js' import {DeploymentStrategy} from '../types/deploymentStrategy'
import { import {
parseTrafficSplitMethod, parseTrafficSplitMethod,
TrafficSplitMethod TrafficSplitMethod
} from '../types/trafficSplitMethod.js' } from '../types/trafficSplitMethod'
import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy.js' import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy'
export async function reject( export async function reject(
kubectl: Kubectl, kubectl: Kubectl,
+2 -5
View File
@@ -1,8 +1,5 @@
import {parseResourceTypeInput} from './inputUtils.js' import {parseResourceTypeInput} from './inputUtils'
import { import {ResourceTypeFleet, ResourceTypeManagedCluster} from './actions/deploy'
ResourceTypeFleet,
ResourceTypeManagedCluster
} from './actions/deploy.js'
describe('InputUtils', () => { describe('InputUtils', () => {
describe('parseResourceTypeInput', () => { describe('parseResourceTypeInput', () => {
+2 -5
View File
@@ -1,9 +1,6 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {parseAnnotations} from './types/annotations.js' import {parseAnnotations} from './types/annotations'
import { import {ResourceTypeFleet, ResourceTypeManagedCluster} from './actions/deploy'
ResourceTypeFleet,
ResourceTypeManagedCluster
} from './actions/deploy.js'
export const inputAnnotations = parseAnnotations( export const inputAnnotations = parseAnnotations(
core.getInput('annotations', {required: false}) core.getInput('annotations', {required: false})
+11 -11
View File
@@ -1,19 +1,19 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {getKubectlPath, Kubectl} from './types/kubectl.js' import {getKubectlPath, Kubectl} from './types/kubectl'
import { import {
deploy, deploy,
ResourceTypeFleet, ResourceTypeFleet,
ResourceTypeManagedCluster ResourceTypeManagedCluster
} from './actions/deploy.js' } from './actions/deploy'
import {ClusterType} from './inputUtils.js' import {ClusterType} from './inputUtils'
import {promote} from './actions/promote.js' import {promote} from './actions/promote'
import {reject} from './actions/reject.js' import {reject} from './actions/reject'
import {Action, parseAction} from './types/action.js' import {Action, parseAction} from './types/action'
import {parseDeploymentStrategy} from './types/deploymentStrategy.js' import {parseDeploymentStrategy} from './types/deploymentStrategy'
import {getFilesFromDirectoriesAndURLs} from './utilities/fileUtils.js' import {getFilesFromDirectoriesAndURLs} from './utilities/fileUtils'
import {PrivateKubectl} from './types/privatekubectl.js' import {PrivateKubectl} from './types/privatekubectl'
import {parseResourceTypeInput} from './inputUtils.js' import {parseResourceTypeInput} from './inputUtils'
import {parseDuration} from './utilities/durationUtils.js' import {parseDuration} from './utilities/durationUtils'
export async function run() { export async function run() {
// verify kubeconfig is set // verify kubeconfig is set
@@ -1,5 +1,3 @@
import {vi} from 'vitest'
import type {MockInstance} from 'vitest'
import { import {
deployWithLabel, deployWithLabel,
deleteGreenObjects, deleteGreenObjects,
@@ -10,16 +8,16 @@ import {
getNewBlueGreenObject, getNewBlueGreenObject,
GREEN_LABEL_VALUE, GREEN_LABEL_VALUE,
isServiceRouted isServiceRouted
} from './blueGreenHelper.js' } from './blueGreenHelper'
import {BlueGreenDeployment} from '../../types/blueGreenTypes.js' import {BlueGreenDeployment} from '../../types/blueGreenTypes'
import * as bgHelper from './blueGreenHelper.js' import * as bgHelper from './blueGreenHelper'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import * as fileHelper from '../../utilities/fileUtils.js' import * as fileHelper from '../../utilities/fileUtils'
import {K8sObject} from '../../types/k8sObject.js' import {K8sObject} from '../../types/k8sObject'
import * as manifestUpdateUtils from '../../utilities/manifestUpdateUtils.js' import * as manifestUpdateUtils from '../../utilities/manifestUpdateUtils'
import {ExecOutput} from '@actions/exec' import {ExecOutput} from '@actions/exec'
vi.mock('../../types/kubectl') jest.mock('../../types/kubectl')
const kubectl = new Kubectl('') const kubectl = new Kubectl('')
const TEST_TIMEOUT = '60s' const TEST_TIMEOUT = '60s'
@@ -39,17 +37,17 @@ const MOCK_EXEC_OUTPUT = {
describe('bluegreenhelper functions', () => { describe('bluegreenhelper functions', () => {
let testObjects let testObjects
beforeEach(() => { beforeEach(() => {
vi.restoreAllMocks() //@ts-ignore
vi.mocked(Kubectl).mockClear() Kubectl.mockClear()
testObjects = getManifestObjects(['test/unit/manifests/test-ingress.yml']) 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 () => { 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( const value = await deleteGreenObjects(
kubectl, kubectl,
@@ -67,16 +65,21 @@ describe('bluegreenhelper functions', () => {
}) })
test('handles timeout when deleting objects', async () => { test('handles timeout when deleting objects', async () => {
const deleteMock = vi.fn().mockResolvedValue(MOCK_EXEC_OUTPUT) // Mock deleteObjects to prevent actual execution
kubectl.delete = deleteMock 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) // Verify kubectl.delete is called with timeout for each object in deleteList
expect(deleteSpy).toHaveBeenCalledTimes(EXPECTED_GREEN_OBJECTS.length)
expect(deleteMock).toHaveBeenCalledTimes(deleteList.length) EXPECTED_GREEN_OBJECTS.forEach(({name, kind}) => {
deleteList.forEach(({name, kind}) => { expect(deleteSpy).toHaveBeenCalledWith(
expect(deleteMock).toHaveBeenCalledWith(
[kind, name], [kind, name],
undefined, undefined,
TEST_TIMEOUT TEST_TIMEOUT
@@ -132,7 +135,7 @@ describe('bluegreenhelper functions', () => {
}) })
test('correctly makes labeled workloads', async () => { 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', stdout: 'deployment.apps/nginx-deployment created',
stderr: '', stderr: '',
exitCode: 0 exitCode: 0
@@ -176,9 +179,9 @@ describe('bluegreenhelper functions', () => {
exitCode: 0 exitCode: 0
} }
vi.spyOn(kubectl, 'getResource').mockImplementation(() => jest
Promise.resolve(mockExecOutput) .spyOn(kubectl, 'getResource')
) .mockImplementation(() => Promise.resolve(mockExecOutput))
const fetched = await fetchResource( const fetched = await fetchResource(
kubectl, kubectl,
'nginx-deployment', 'nginx-deployment',
@@ -206,12 +209,12 @@ describe('bluegreenhelper functions', () => {
] ]
for (const testCase of errorTestCases) { for (const testCase of errorTestCases) {
const spy = vi.spyOn(kubectl, 'getResource') const spy = jest.spyOn(kubectl, 'getResource')
if (testCase.mockOutput) { if (testCase.mockOutput) {
spy.mockImplementation(() => Promise.resolve(testCase.mockOutput)) spy.mockImplementation(() => Promise.resolve(testCase.mockOutput))
} else { } else {
spy.mockResolvedValue(null) spy.mockImplementation()
} }
const fetched = await fetchResource( const fetched = await fetchResource(
@@ -232,13 +235,12 @@ describe('bluegreenhelper functions', () => {
stderr: '' stderr: ''
} as ExecOutput } as ExecOutput
vi.spyOn(kubectl, 'getResource').mockResolvedValue(mockExecOutput) jest.spyOn(kubectl, 'getResource').mockResolvedValue(mockExecOutput)
vi.spyOn( jest
manifestUpdateUtils, .spyOn(manifestUpdateUtils, 'UnsetClusterSpecificDetails')
'UnsetClusterSpecificDetails' .mockImplementation(() => {
).mockImplementation(() => { throw new Error('test error')
throw new Error('test error') })
})
expect( expect(
await fetchResource(kubectl, 'nginx-deployment', 'Deployment') await fetchResource(kubectl, 'nginx-deployment', 'Deployment')
@@ -265,7 +267,7 @@ describe('bluegreenhelper functions', () => {
describe('deployObjects', () => { describe('deployObjects', () => {
let mockObjects: any[] let mockObjects: any[]
let kubectlApplySpy: MockInstance let kubectlApplySpy: jest.SpyInstance
const mockSuccessResult: ExecOutput = { const mockSuccessResult: ExecOutput = {
stdout: 'deployment.apps/nginx-deployment created', stdout: 'deployment.apps/nginx-deployment created',
@@ -283,11 +285,11 @@ describe('bluegreenhelper functions', () => {
// //@ts-ignore // //@ts-ignore
// Kubectl.mockClear() // Kubectl.mockClear()
mockObjects = [testObjects.deploymentEntityList[0]] mockObjects = [testObjects.deploymentEntityList[0]]
kubectlApplySpy = vi.spyOn(kubectl, 'apply') kubectlApplySpy = jest.spyOn(kubectl, 'apply')
}) })
afterEach(() => { afterEach(() => {
vi.clearAllMocks() jest.clearAllMocks()
}) })
it('should return execution result and manifest files when kubectl apply succeeds', async () => { 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 fs from 'fs'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import {DeployResult} from '../../types/deployResult.js' import {DeployResult} from '../../types/deployResult'
import {K8sObject, K8sDeleteObject} from '../../types/k8sObject.js' import {K8sObject, K8sDeleteObject} from '../../types/k8sObject'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import { import {
isDeploymentEntity, isDeploymentEntity,
isIngressEntity, isIngressEntity,
isServiceEntity, isServiceEntity,
KubernetesWorkload KubernetesWorkload
} from '../../types/kubernetesTypes.js' } from '../../types/kubernetesTypes'
import { import {
BlueGreenDeployment, BlueGreenDeployment,
BlueGreenManifests BlueGreenManifests
} from '../../types/blueGreenTypes.js' } from '../../types/blueGreenTypes'
import * as fileHelper from '../../utilities/fileUtils.js' import * as fileHelper from '../../utilities/fileUtils'
import {updateSpecLabels} from '../../utilities/manifestSpecLabelUtils.js' import {updateSpecLabels} from '../../utilities/manifestSpecLabelUtils'
import {checkForErrors} from '../../utilities/kubectlUtils.js' import {checkForErrors} from '../../utilities/kubectlUtils'
import { import {
UnsetClusterSpecificDetails, UnsetClusterSpecificDetails,
updateObjectLabels, updateObjectLabels,
updateSelectorLabels updateSelectorLabels
} from '../../utilities/manifestUpdateUtils.js' } from '../../utilities/manifestUpdateUtils'
export const GREEN_LABEL_VALUE = 'green' export const GREEN_LABEL_VALUE = 'green'
export const NONE_LABEL_VALUE = 'None' export const NONE_LABEL_VALUE = 'None'
+52 -52
View File
@@ -1,23 +1,21 @@
import {vi} from 'vitest' import {BlueGreenDeployment} from '../../types/blueGreenTypes'
import type {MockInstance} from 'vitest'
import {BlueGreenDeployment} from '../../types/blueGreenTypes.js'
import { import {
deployBlueGreen, deployBlueGreen,
deployBlueGreenIngress, deployBlueGreenIngress,
deployBlueGreenService, deployBlueGreenService,
deployBlueGreenSMI deployBlueGreenSMI
} from './deploy.js' } from './deploy'
import * as routeTester from './route.js' import * as routeTester from './route'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import {RouteStrategy} from '../../types/routeStrategy.js' import {RouteStrategy} from '../../types/routeStrategy'
import * as TSutils from '../../utilities/trafficSplitUtils.js' import * as TSutils from '../../utilities/trafficSplitUtils'
import * as bgHelper from './blueGreenHelper.js' import * as bgHelper from './blueGreenHelper'
import * as smiHelper from './smiBlueGreenHelper.js' import * as smiHelper from './smiBlueGreenHelper'
import {ExecOutput} from '@actions/exec' import {ExecOutput} from '@actions/exec'
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml'] 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 // Shared variables and mock objects used across all test suites
const mockDeployResult = { const mockDeployResult = {
@@ -32,7 +30,7 @@ const mockBgDeployment: BlueGreenDeployment = {
describe('deploy tests', () => { describe('deploy tests', () => {
let kubectl: Kubectl let kubectl: Kubectl
let kubectlApplySpy: MockInstance let kubectlApplySpy: jest.SpyInstance
const mockSuccessResult: ExecOutput = { const mockSuccessResult: ExecOutput = {
stdout: 'deployment.apps/nginx-deployment created', stdout: 'deployment.apps/nginx-deployment created',
@@ -47,20 +45,21 @@ describe('deploy tests', () => {
} }
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() //@ts-ignore
Kubectl.mockClear()
kubectl = new Kubectl('') kubectl = new Kubectl('')
kubectlApplySpy = vi.spyOn(kubectl, 'apply') kubectlApplySpy = jest.spyOn(kubectl, 'apply')
}) })
test('correctly determines deploy type and acts accordingly', async () => { test('correctly determines deploy type and acts accordingly', async () => {
kubectlApplySpy.mockResolvedValue(mockSuccessResult) kubectlApplySpy.mockResolvedValue(mockSuccessResult)
vi.spyOn(routeTester, 'routeBlueGreenForDeploy').mockImplementation(() => jest
Promise.resolve(mockBgDeployment) .spyOn(routeTester, 'routeBlueGreenForDeploy')
) .mockImplementation(() => Promise.resolve(mockBgDeployment))
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(() => jest
Promise.resolve('v1alpha3') .spyOn(TSutils, 'getTrafficSplitAPIVersion')
) .mockImplementation(() => Promise.resolve('v1alpha3'))
const ingressResult = await deployBlueGreen( const ingressResult = await deployBlueGreen(
kubectl, kubectl,
@@ -113,12 +112,12 @@ describe('deploy tests', () => {
fn: () => fn: () =>
deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.INGRESS), deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.INGRESS),
setup: () => { setup: () => {
vi.spyOn(routeTester, 'routeBlueGreenForDeploy').mockImplementation( jest
() => Promise.resolve(mockBgDeployment) .spyOn(routeTester, 'routeBlueGreenForDeploy')
) .mockImplementation(() => Promise.resolve(mockBgDeployment))
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation( jest
() => Promise.resolve('v1alpha3') .spyOn(TSutils, 'getTrafficSplitAPIVersion')
) .mockImplementation(() => Promise.resolve('v1alpha3'))
} }
}, },
{ {
@@ -126,24 +125,24 @@ describe('deploy tests', () => {
fn: () => fn: () =>
deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.SERVICE), deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.SERVICE),
setup: () => { setup: () => {
vi.spyOn(routeTester, 'routeBlueGreenForDeploy').mockImplementation( jest
() => Promise.resolve(mockBgDeployment) .spyOn(routeTester, 'routeBlueGreenForDeploy')
) .mockImplementation(() => Promise.resolve(mockBgDeployment))
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation( jest
() => Promise.resolve('v1alpha3') .spyOn(TSutils, 'getTrafficSplitAPIVersion')
) .mockImplementation(() => Promise.resolve('v1alpha3'))
} }
}, },
{ {
name: 'should throw error when kubectl apply fails during blue/green deployment with SMI strategy', name: 'should throw error when kubectl apply fails during blue/green deployment with SMI strategy',
fn: () => deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.SMI), fn: () => deployBlueGreen(kubectl, ingressFilepath, RouteStrategy.SMI),
setup: () => { setup: () => {
vi.spyOn(routeTester, 'routeBlueGreenForDeploy').mockImplementation( jest
() => Promise.resolve(mockBgDeployment) .spyOn(routeTester, 'routeBlueGreenForDeploy')
) .mockImplementation(() => Promise.resolve(mockBgDeployment))
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation( jest
() => Promise.resolve('v1alpha3') .spyOn(TSutils, 'getTrafficSplitAPIVersion')
) .mockImplementation(() => Promise.resolve('v1alpha3'))
} }
}, },
{ {
@@ -182,7 +181,8 @@ describe('deploy timeout tests', () => {
let kubectl: Kubectl let kubectl: Kubectl
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() //@ts-ignore
Kubectl.mockClear()
kubectl = new Kubectl('') kubectl = new Kubectl('')
}) })
@@ -190,16 +190,16 @@ describe('deploy timeout tests', () => {
const timeout = '300s' const timeout = '300s'
// Mock the helper functions that are actually called // Mock the helper functions that are actually called
const deployWithLabelSpy = vi const deployWithLabelSpy = jest
.spyOn(bgHelper, 'deployWithLabel') .spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment) .mockResolvedValue(mockBgDeployment)
const deployObjectsSpy = vi const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects') .spyOn(bgHelper, 'deployObjects')
.mockResolvedValue(mockDeployResult) .mockResolvedValue(mockDeployResult)
const setupSMISpy = vi const setupSMISpy = jest
.spyOn(smiHelper, 'setupSMI') .spyOn(smiHelper, 'setupSMI')
.mockResolvedValue(mockBgDeployment) .mockResolvedValue(mockBgDeployment)
const routeSpy = vi const routeSpy = jest
.spyOn(routeTester, 'routeBlueGreenForDeploy') .spyOn(routeTester, 'routeBlueGreenForDeploy')
.mockResolvedValue(mockBgDeployment) .mockResolvedValue(mockBgDeployment)
@@ -258,10 +258,10 @@ describe('deploy timeout tests', () => {
const timeout = '240s' const timeout = '240s'
// Mock the dependencies // Mock the dependencies
const deployWithLabelSpy = vi const deployWithLabelSpy = jest
.spyOn(bgHelper, 'deployWithLabel') .spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment) .mockResolvedValue(mockBgDeployment)
const deployObjectsSpy = vi const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects') .spyOn(bgHelper, 'deployObjects')
.mockResolvedValue(mockDeployResult) .mockResolvedValue(mockDeployResult)
@@ -290,10 +290,10 @@ describe('deploy timeout tests', () => {
const timeout = '180s' const timeout = '180s'
// Mock the dependencies // Mock the dependencies
const deployWithLabelSpy = vi const deployWithLabelSpy = jest
.spyOn(bgHelper, 'deployWithLabel') .spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment) .mockResolvedValue(mockBgDeployment)
const deployObjectsSpy = vi const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects') .spyOn(bgHelper, 'deployObjects')
.mockResolvedValue(mockDeployResult) .mockResolvedValue(mockDeployResult)
@@ -322,13 +322,13 @@ describe('deploy timeout tests', () => {
const timeout = '360s' const timeout = '360s'
// Mock the dependencies // Mock the dependencies
const setupSMISpy = vi const setupSMISpy = jest
.spyOn(smiHelper, 'setupSMI') .spyOn(smiHelper, 'setupSMI')
.mockResolvedValue(mockBgDeployment) .mockResolvedValue(mockBgDeployment)
const deployObjectsSpy = vi const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects') .spyOn(bgHelper, 'deployObjects')
.mockResolvedValue(mockDeployResult) .mockResolvedValue(mockDeployResult)
const deployWithLabelSpy = vi const deployWithLabelSpy = jest
.spyOn(bgHelper, 'deployWithLabel') .spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment) .mockResolvedValue(mockBgDeployment)
@@ -354,10 +354,10 @@ describe('deploy timeout tests', () => {
}) })
test('deploy functions without timeout should pass undefined', async () => { test('deploy functions without timeout should pass undefined', async () => {
const deployWithLabelSpy = vi const deployWithLabelSpy = jest
.spyOn(bgHelper, 'deployWithLabel') .spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment) .mockResolvedValue(mockBgDeployment)
const deployObjectsSpy = vi const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects') .spyOn(bgHelper, 'deployObjects')
.mockResolvedValue(mockDeployResult) .mockResolvedValue(mockDeployResult)
+7 -7
View File
@@ -1,23 +1,23 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import { import {
BlueGreenDeployment, BlueGreenDeployment,
BlueGreenManifests BlueGreenManifests
} from '../../types/blueGreenTypes.js' } from '../../types/blueGreenTypes'
import {RouteStrategy} from '../../types/routeStrategy.js' import {RouteStrategy} from '../../types/routeStrategy'
import { import {
deployWithLabel, deployWithLabel,
getManifestObjects, getManifestObjects,
GREEN_LABEL_VALUE, GREEN_LABEL_VALUE,
deployObjects deployObjects
} from './blueGreenHelper.js' } from './blueGreenHelper'
import {setupSMI} from './smiBlueGreenHelper.js' import {setupSMI} from './smiBlueGreenHelper'
import {routeBlueGreenForDeploy} from './route.js' import {routeBlueGreenForDeploy} from './route'
import {DeployResult} from '../../types/deployResult.js' import {DeployResult} from '../../types/deployResult'
export async function deployBlueGreen( export async function deployBlueGreen(
kubectl: Kubectl, kubectl: Kubectl,
@@ -1,27 +1,27 @@
import {vi} from 'vitest' import {getManifestObjects, GREEN_LABEL_VALUE} from './blueGreenHelper'
import {getManifestObjects, GREEN_LABEL_VALUE} from './blueGreenHelper.js' import * as bgHelper from './blueGreenHelper'
import * as bgHelper from './blueGreenHelper.js'
import { import {
getUpdatedBlueGreenIngress, getUpdatedBlueGreenIngress,
isIngressRouted, isIngressRouted,
validateIngresses validateIngresses
} from './ingressBlueGreenHelper.js' } from './ingressBlueGreenHelper'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import * as fileHelper from '../../utilities/fileUtils.js' import * as fileHelper from '../../utilities/fileUtils'
const betaFilepath = ['test/unit/manifests/test-ingress.yml'] const betaFilepath = ['test/unit/manifests/test-ingress.yml']
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml'] const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
const kubectl = new Kubectl('') const kubectl = new Kubectl('')
vi.mock('../../types/kubectl') jest.mock('../../types/kubectl')
describe('ingress blue green helpers', () => { describe('ingress blue green helpers', () => {
let testObjects let testObjects
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() //@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath) testObjects = getManifestObjects(ingressFilepath)
vi.spyOn(fileHelper, 'writeObjectsToFile').mockImplementationOnce(() => [ jest
'' .spyOn(fileHelper, 'writeObjectsToFile')
]) .mockImplementationOnce(() => [''])
}) })
test('it should correctly classify ingresses', () => { test('it should correctly classify ingresses', () => {
@@ -72,7 +72,7 @@ describe('ingress blue green helpers', () => {
test('it should validate ingresses', async () => { test('it should validate ingresses', async () => {
// what if nothing gets returned from fetchResource? // what if nothing gets returned from fetchResource?
vi.spyOn(bgHelper, 'fetchResource').mockResolvedValue(null) jest.spyOn(bgHelper, 'fetchResource').mockImplementation()
let validResponse = await validateIngresses( let validResponse = await validateIngresses(
kubectl, kubectl,
testObjects.ingressEntityList, testObjects.ingressEntityList,
@@ -89,9 +89,9 @@ describe('ingress blue green helpers', () => {
const mockLabels = new Map<string, string>() const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = GREEN_LABEL_VALUE mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = GREEN_LABEL_VALUE
mockIngress.metadata.labels = mockLabels mockIngress.metadata.labels = mockLabels
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() => jest
Promise.resolve(mockIngress) .spyOn(bgHelper, 'fetchResource')
) .mockImplementation(() => Promise.resolve(mockIngress))
validResponse = await validateIngresses( validResponse = await validateIngresses(
kubectl, kubectl,
testObjects.ingressEntityList, testObjects.ingressEntityList,
@@ -1,12 +1,12 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {K8sIngress} from '../../types/k8sObject.js' import {K8sIngress} from '../../types/k8sObject'
import { import {
addBlueGreenLabelsAndAnnotations, addBlueGreenLabelsAndAnnotations,
BLUE_GREEN_VERSION_LABEL, BLUE_GREEN_VERSION_LABEL,
GREEN_LABEL_VALUE, GREEN_LABEL_VALUE,
fetchResource fetchResource
} from './blueGreenHelper.js' } from './blueGreenHelper'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
const BACKEND = 'backend' const BACKEND = 'backend'
+53 -47
View File
@@ -1,22 +1,20 @@
import {vi} from 'vitest' import {getManifestObjects} from './blueGreenHelper'
import type {MockInstance} from 'vitest'
import {getManifestObjects} from './blueGreenHelper.js'
import { import {
promoteBlueGreenIngress, promoteBlueGreenIngress,
promoteBlueGreenService, promoteBlueGreenService,
promoteBlueGreenSMI promoteBlueGreenSMI
} from './promote.js' } from './promote'
import {TrafficSplitObject} from '../../types/k8sObject.js' import {TrafficSplitObject} from '../../types/k8sObject'
import * as servicesTester from './serviceBlueGreenHelper.js' import * as servicesTester from './serviceBlueGreenHelper'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import {MAX_VAL, MIN_VAL, TRAFFIC_SPLIT_OBJECT} from './smiBlueGreenHelper.js' import {MAX_VAL, MIN_VAL, TRAFFIC_SPLIT_OBJECT} from './smiBlueGreenHelper'
import * as smiTester from './smiBlueGreenHelper.js' import * as smiTester from './smiBlueGreenHelper'
import * as bgHelper from './blueGreenHelper.js' import * as bgHelper from './blueGreenHelper'
import {ExecOutput} from '@actions/exec' import {ExecOutput} from '@actions/exec'
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml'] const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
vi.mock('../../types/kubectl') jest.mock('../../types/kubectl')
// Shared variables used across all test suites // Shared variables used across all test suites
let testObjects: any let testObjects: any
@@ -44,12 +42,13 @@ const mockBgDeployment = {
} }
describe('promote tests', () => { describe('promote tests', () => {
let kubectlApplySpy: MockInstance let kubectlApplySpy: jest.SpyInstance
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() //@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath) testObjects = getManifestObjects(ingressFilepath)
kubectlApplySpy = vi.spyOn(kubectl, 'apply') kubectlApplySpy = jest.spyOn(kubectl, 'apply')
}) })
test('promote blue/green ingress', async () => { test('promote blue/green ingress', async () => {
@@ -58,7 +57,7 @@ describe('promote tests', () => {
const mockLabels = new Map<string, string>() const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.GREEN_LABEL_VALUE mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.GREEN_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() => jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({ Promise.resolve({
kind: 'Ingress', kind: 'Ingress',
spec: {}, spec: {},
@@ -83,7 +82,7 @@ describe('promote tests', () => {
test('fail to promote invalid blue/green ingress', async () => { test('fail to promote invalid blue/green ingress', async () => {
const mockLabels = new Map<string, string>() const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.NONE_LABEL_VALUE mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.NONE_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() => jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({ Promise.resolve({
kind: 'Ingress', kind: 'Ingress',
spec: {}, spec: {},
@@ -101,7 +100,7 @@ describe('promote tests', () => {
const mockLabels = new Map<string, string>() const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.GREEN_LABEL_VALUE mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.GREEN_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() => jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({ Promise.resolve({
kind: 'Service', kind: 'Service',
spec: {selector: mockLabels}, spec: {selector: mockLabels},
@@ -121,16 +120,16 @@ describe('promote tests', () => {
test('fail to promote invalid blue/green service', async () => { test('fail to promote invalid blue/green service', async () => {
const mockLabels = new Map<string, string>() const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.NONE_LABEL_VALUE mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.NONE_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() => jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({ Promise.resolve({
kind: 'Service', kind: 'Service',
spec: {}, spec: {},
metadata: {labels: mockLabels, name: 'nginx-ingress-green'} metadata: {labels: mockLabels, name: 'nginx-ingress-green'}
}) })
) )
vi.spyOn(servicesTester, 'validateServicesState').mockImplementationOnce( jest
() => Promise.resolve(false) .spyOn(servicesTester, 'validateServicesState')
) .mockImplementationOnce(() => Promise.resolve(false))
await expect( await expect(
promoteBlueGreenService(kubectl, testObjects) promoteBlueGreenService(kubectl, testObjects)
@@ -165,9 +164,9 @@ describe('promote tests', () => {
] ]
} }
} }
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() => jest
Promise.resolve(mockTsObject) .spyOn(bgHelper, 'fetchResource')
) .mockImplementation(() => Promise.resolve(mockTsObject))
const deployResult = await promoteBlueGreenSMI(kubectl, testObjects) const deployResult = await promoteBlueGreenSMI(kubectl, testObjects)
@@ -183,11 +182,11 @@ describe('promote tests', () => {
test('promote blue/green SMI with bad trafficsplit', async () => { test('promote blue/green SMI with bad trafficsplit', async () => {
const mockLabels = new Map<string, string>() const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.NONE_LABEL_VALUE mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = bgHelper.NONE_LABEL_VALUE
vi.spyOn(smiTester, 'validateTrafficSplitsState').mockImplementation(() => jest
Promise.resolve(false) .spyOn(smiTester, 'validateTrafficSplitsState')
) .mockImplementation(() => Promise.resolve(false))
await expect(promoteBlueGreenSMI(kubectl, testObjects)).rejects.toThrow() expect(promoteBlueGreenSMI(kubectl, testObjects)).rejects.toThrow()
}) })
// Consolidated error tests // Consolidated error tests
@@ -199,7 +198,7 @@ describe('promote tests', () => {
const mockLabels = new Map<string, string>() const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] =
bgHelper.GREEN_LABEL_VALUE bgHelper.GREEN_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() => jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({ Promise.resolve({
kind: 'Ingress', kind: 'Ingress',
spec: {}, spec: {},
@@ -215,16 +214,16 @@ describe('promote tests', () => {
const mockLabels = new Map<string, string>() const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] =
bgHelper.GREEN_LABEL_VALUE bgHelper.GREEN_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() => jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({ Promise.resolve({
kind: 'Service', kind: 'Service',
spec: {selector: mockLabels}, spec: {selector: mockLabels},
metadata: {labels: mockLabels, name: 'nginx-service-green'} metadata: {labels: mockLabels, name: 'nginx-service-green'}
}) })
) )
vi.spyOn(servicesTester, 'validateServicesState').mockResolvedValue( jest
true .spyOn(servicesTester, 'validateServicesState')
) .mockResolvedValue(true)
} }
}, },
{ {
@@ -247,10 +246,12 @@ describe('promote tests', () => {
] ]
} }
} }
vi.spyOn(bgHelper, 'fetchResource').mockResolvedValue(mockTsObject) jest
vi.spyOn(smiTester, 'validateTrafficSplitsState').mockResolvedValue( .spyOn(bgHelper, 'fetchResource')
true .mockResolvedValue(mockTsObject)
) jest
.spyOn(smiTester, 'validateTrafficSplitsState')
.mockResolvedValue(true)
} }
} }
])('$name', async ({fn, setup}) => { ])('$name', async ({fn, setup}) => {
@@ -278,12 +279,15 @@ describe('promote tests', () => {
// Timeout tests // Timeout tests
describe('promote timeout tests', () => { describe('promote timeout tests', () => {
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() // @ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath) testObjects = getManifestObjects(ingressFilepath)
}) })
const mockDeployWithLabel = () => const mockDeployWithLabel = () =>
vi.spyOn(bgHelper, 'deployWithLabel').mockResolvedValue(mockBgDeployment) jest
.spyOn(bgHelper, 'deployWithLabel')
.mockResolvedValue(mockBgDeployment)
const setupFetchResource = ( const setupFetchResource = (
kind: string, kind: string,
@@ -293,7 +297,7 @@ describe('promote timeout tests', () => {
const mockLabels = new Map<string, string>() const mockLabels = new Map<string, string>()
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = labelValue mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = labelValue
vi.spyOn(bgHelper, 'fetchResource').mockResolvedValue({ jest.spyOn(bgHelper, 'fetchResource').mockResolvedValue({
kind, kind,
spec: {}, spec: {},
metadata: {labels: mockLabels, name} metadata: {labels: mockLabels, name}
@@ -326,9 +330,9 @@ describe('promote timeout tests', () => {
'nginx-service-green', 'nginx-service-green',
bgHelper.GREEN_LABEL_VALUE bgHelper.GREEN_LABEL_VALUE
) )
vi.spyOn(servicesTester, 'validateServicesState').mockResolvedValue( jest
true .spyOn(servicesTester, 'validateServicesState')
) .mockResolvedValue(true)
} }
}, },
{ {
@@ -355,10 +359,12 @@ describe('promote timeout tests', () => {
} }
} }
vi.spyOn(bgHelper, 'fetchResource').mockResolvedValue(mockTsObject) jest
vi.spyOn(smiTester, 'validateTrafficSplitsState').mockResolvedValue( .spyOn(bgHelper, 'fetchResource')
true .mockResolvedValue(mockTsObject)
) jest
.spyOn(smiTester, 'validateTrafficSplitsState')
.mockResolvedValue(true)
} }
} }
])('$name', async ({fn, timeout, setup}) => { ])('$name', async ({fn, timeout, setup}) => {
+6 -6
View File
@@ -1,13 +1,13 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import {BlueGreenDeployment} from '../../types/blueGreenTypes.js' import {BlueGreenDeployment} from '../../types/blueGreenTypes'
import {deployWithLabel, NONE_LABEL_VALUE} from './blueGreenHelper.js' import {deployWithLabel, NONE_LABEL_VALUE} from './blueGreenHelper'
import {validateIngresses} from './ingressBlueGreenHelper.js' import {validateIngresses} from './ingressBlueGreenHelper'
import {validateServicesState} from './serviceBlueGreenHelper.js' import {validateServicesState} from './serviceBlueGreenHelper'
import {validateTrafficSplitsState} from './smiBlueGreenHelper.js' import {validateTrafficSplitsState} from './smiBlueGreenHelper'
export async function promoteBlueGreenIngress( export async function promoteBlueGreenIngress(
kubectl: Kubectl, kubectl: Kubectl,
+39 -38
View File
@@ -1,23 +1,21 @@
import {vi} from 'vitest' import {getManifestObjects} from './blueGreenHelper'
import type {MockInstance} from 'vitest' import {Kubectl} from '../../types/kubectl'
import {getManifestObjects} from './blueGreenHelper.js'
import {Kubectl} from '../../types/kubectl.js'
import * as TSutils from '../../utilities/trafficSplitUtils.js' import * as TSutils from '../../utilities/trafficSplitUtils'
import { import {
rejectBlueGreenIngress, rejectBlueGreenIngress,
rejectBlueGreenService, rejectBlueGreenService,
rejectBlueGreenSMI rejectBlueGreenSMI
} from './reject.js' } from './reject'
import * as bgHelper from './blueGreenHelper.js' import * as bgHelper from './blueGreenHelper'
import * as routeHelper from './route.js' import * as routeHelper from './route'
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml'] const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
const kubectl = new Kubectl('') const kubectl = new Kubectl('')
const TEST_TIMEOUT_SHORT = '60s' const TEST_TIMEOUT_SHORT = '60s'
const TEST_TIMEOUT_LONG = '120s' const TEST_TIMEOUT_LONG = '120s'
vi.mock('../../types/kubectl') jest.mock('../../types/kubectl')
// Shared mock objects following DRY principle // Shared mock objects following DRY principle
const mockSuccessResult = { const mockSuccessResult = {
@@ -56,13 +54,14 @@ const mockDeleteResult = [
describe('reject tests', () => { describe('reject tests', () => {
let testObjects: any let testObjects: any
let kubectlApplySpy: MockInstance let kubectlApplySpy: jest.SpyInstance
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() //@ts-ignore
vi.restoreAllMocks() Kubectl.mockClear()
jest.restoreAllMocks()
testObjects = getManifestObjects(ingressFilepath) testObjects = getManifestObjects(ingressFilepath)
kubectlApplySpy = vi.spyOn(kubectl, 'apply') kubectlApplySpy = jest.spyOn(kubectl, 'apply')
}) })
test('reject blue/green ingress', async () => { test('reject blue/green ingress', async () => {
@@ -90,13 +89,13 @@ describe('reject tests', () => {
test('reject blue/green ingress with timeout', async () => { test('reject blue/green ingress with timeout', async () => {
// Mock routeBlueGreenIngressUnchanged and deleteGreenObjects // Mock routeBlueGreenIngressUnchanged and deleteGreenObjects
vi.spyOn(routeHelper, 'routeBlueGreenIngressUnchanged').mockResolvedValue( jest
mockBgDeployment .spyOn(routeHelper, 'routeBlueGreenIngressUnchanged')
) .mockResolvedValue(mockBgDeployment)
vi.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue( jest
mockDeleteResult .spyOn(bgHelper, 'deleteGreenObjects')
) .mockResolvedValue(mockDeleteResult)
const value = await rejectBlueGreenIngress( const value = await rejectBlueGreenIngress(
kubectl, kubectl,
@@ -139,9 +138,9 @@ describe('reject tests', () => {
test('reject blue/green service', async () => { test('reject blue/green service', async () => {
kubectlApplySpy.mockResolvedValue(mockSuccessResult) kubectlApplySpy.mockResolvedValue(mockSuccessResult)
vi.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue( jest
mockDeleteResult .spyOn(bgHelper, 'deleteGreenObjects')
) .mockResolvedValue(mockDeleteResult)
const value = await rejectBlueGreenService( const value = await rejectBlueGreenService(
kubectl, kubectl,
@@ -164,7 +163,7 @@ describe('reject tests', () => {
test('reject blue/green service with timeout', async () => { test('reject blue/green service with timeout', async () => {
// Mock routeBlueGreenService and deleteGreenObjects // Mock routeBlueGreenService and deleteGreenObjects
vi.spyOn(routeHelper, 'routeBlueGreenService').mockResolvedValue({ jest.spyOn(routeHelper, 'routeBlueGreenService').mockResolvedValue({
deployResult: { deployResult: {
execResult: {stdout: '', stderr: '', exitCode: 0}, execResult: {stdout: '', stderr: '', exitCode: 0},
manifestFiles: [] manifestFiles: []
@@ -181,9 +180,11 @@ describe('reject tests', () => {
] ]
}) })
vi.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue([ jest
{name: 'nginx-deployment-green', kind: 'Deployment'} .spyOn(bgHelper, 'deleteGreenObjects')
]) .mockResolvedValue([
{name: 'nginx-deployment-green', kind: 'Deployment'}
])
const value = await rejectBlueGreenService( const value = await rejectBlueGreenService(
kubectl, kubectl,
@@ -212,9 +213,9 @@ describe('reject tests', () => {
// Mock kubectl.apply to return successful result // Mock kubectl.apply to return successful result
kubectlApplySpy.mockResolvedValue(mockSuccessResult) kubectlApplySpy.mockResolvedValue(mockSuccessResult)
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(() => jest
Promise.resolve('v1alpha3') .spyOn(TSutils, 'getTrafficSplitAPIVersion')
) .mockImplementation(() => Promise.resolve('v1alpha3'))
const rejectResult = await rejectBlueGreenSMI(kubectl, testObjects) const rejectResult = await rejectBlueGreenSMI(kubectl, testObjects)
expect(rejectResult.deleteResult).toHaveLength(2) 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', name: 'should throw error when kubectl apply fails during blue/green ingress rejection',
fn: () => rejectBlueGreenIngress(kubectl, testObjects), fn: () => rejectBlueGreenIngress(kubectl, testObjects),
setup: () => { setup: () => {
vi.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue( jest
mockDeleteResult .spyOn(bgHelper, 'deleteGreenObjects')
) .mockResolvedValue(mockDeleteResult)
} }
}, },
{ {
name: 'should throw error when kubectl apply fails during blue/green service rejection', name: 'should throw error when kubectl apply fails during blue/green service rejection',
fn: () => rejectBlueGreenService(kubectl, testObjects), fn: () => rejectBlueGreenService(kubectl, testObjects),
setup: () => { setup: () => {
vi.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue( jest
mockDeleteResult .spyOn(bgHelper, 'deleteGreenObjects')
) .mockResolvedValue(mockDeleteResult)
} }
}, },
{ {
name: 'should throw error when kubectl apply fails during blue/green SMI rejection', name: 'should throw error when kubectl apply fails during blue/green SMI rejection',
fn: () => rejectBlueGreenSMI(kubectl, testObjects), fn: () => rejectBlueGreenSMI(kubectl, testObjects),
setup: () => { setup: () => {
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation( jest
() => Promise.resolve('v1alpha3') .spyOn(TSutils, 'getTrafficSplitAPIVersion')
) .mockImplementation(() => Promise.resolve('v1alpha3'))
} }
} }
])('$name', async ({fn, setup}) => { ])('$name', async ({fn, setup}) => {
+7 -7
View File
@@ -1,14 +1,14 @@
import {K8sDeleteObject} from '../../types/k8sObject.js' import {K8sDeleteObject} from '../../types/k8sObject'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import { import {
BlueGreenDeployment, BlueGreenDeployment,
BlueGreenManifests, BlueGreenManifests,
BlueGreenRejectResult BlueGreenRejectResult
} from '../../types/blueGreenTypes.js' } from '../../types/blueGreenTypes'
import {deleteGreenObjects, NONE_LABEL_VALUE} from './blueGreenHelper.js' import {deleteGreenObjects, NONE_LABEL_VALUE} from './blueGreenHelper'
import {routeBlueGreenSMI} from './route.js' import {routeBlueGreenSMI} from './route'
import {cleanupSMI} from './smiBlueGreenHelper.js' import {cleanupSMI} from './smiBlueGreenHelper'
import {routeBlueGreenIngressUnchanged, routeBlueGreenService} from './route.js' import {routeBlueGreenIngressUnchanged, routeBlueGreenService} from './route'
export async function rejectBlueGreenIngress( export async function rejectBlueGreenIngress(
kubectl: Kubectl, kubectl: Kubectl,
+43 -51
View File
@@ -1,28 +1,24 @@
import {vi} from 'vitest' import {K8sIngress, TrafficSplitObject} from '../../types/k8sObject'
import type {MockInstance} from 'vitest' import {Kubectl} from '../../types/kubectl'
import {K8sIngress, TrafficSplitObject} from '../../types/k8sObject.js' import * as fileHelper from '../../utilities/fileUtils'
import {Kubectl} from '../../types/kubectl.js' import * as TSutils from '../../utilities/trafficSplitUtils'
import * as fileHelper from '../../utilities/fileUtils.js' import {RouteStrategy} from '../../types/routeStrategy'
import * as TSutils from '../../utilities/trafficSplitUtils.js' import {BlueGreenManifests} from '../../types/blueGreenTypes'
import {RouteStrategy} from '../../types/routeStrategy.js'
import {BlueGreenManifests} from '../../types/blueGreenTypes.js'
import { import {
BLUE_GREEN_VERSION_LABEL, BLUE_GREEN_VERSION_LABEL,
getManifestObjects, getManifestObjects,
GREEN_LABEL_VALUE GREEN_LABEL_VALUE
} from './blueGreenHelper.js' } from './blueGreenHelper'
import * as bgHelper from './blueGreenHelper.js'
import * as smiHelper from './smiBlueGreenHelper.js'
import { import {
routeBlueGreenIngress, routeBlueGreenIngress,
routeBlueGreenService, routeBlueGreenService,
routeBlueGreenForDeploy, routeBlueGreenForDeploy,
routeBlueGreenSMI, routeBlueGreenSMI,
routeBlueGreenIngressUnchanged routeBlueGreenIngressUnchanged
} from './route.js' } from './route'
vi.mock('../../types/kubectl') jest.mock('../../types/kubectl')
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml'] const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
const kc = new Kubectl('') const kc = new Kubectl('')
@@ -41,15 +37,16 @@ const mockFailureResult = {
describe('route function tests', () => { describe('route function tests', () => {
let testObjects: BlueGreenManifests let testObjects: BlueGreenManifests
let kubectlApplySpy: MockInstance let kubectlApplySpy: jest.SpyInstance
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() //@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath) testObjects = getManifestObjects(ingressFilepath)
kubectlApplySpy = vi.spyOn(kc, 'apply') kubectlApplySpy = jest.spyOn(kc, 'apply')
vi.spyOn(fileHelper, 'writeObjectsToFile').mockImplementationOnce(() => [ jest
'' .spyOn(fileHelper, 'writeObjectsToFile')
]) .mockImplementationOnce(() => [''])
}) })
test('correctly prepares blue/green ingresses for deployment', async () => { 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 () => { test('correctly identifies route pattern and acts accordingly', async () => {
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(() => jest
Promise.resolve('v1alpha3') .spyOn(TSutils, 'getTrafficSplitAPIVersion')
) .mockImplementation(() => Promise.resolve('v1alpha3'))
const ingressResult = await routeBlueGreenForDeploy( const ingressResult = await routeBlueGreenForDeploy(
kc, kc,
@@ -167,9 +164,9 @@ describe('route function tests', () => {
testObjects.serviceEntityList testObjects.serviceEntityList
), ),
setup: () => { setup: () => {
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation( jest
() => Promise.resolve('v1alpha3') .spyOn(TSutils, 'getTrafficSplitAPIVersion')
) .mockImplementation(() => Promise.resolve('v1alpha3'))
} }
}, },
{ {
@@ -197,23 +194,24 @@ describe('route timeout tests', () => {
let testObjects: BlueGreenManifests let testObjects: BlueGreenManifests
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() //@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath) testObjects = getManifestObjects(ingressFilepath)
vi.spyOn(fileHelper, 'writeObjectsToFile').mockImplementationOnce(() => [ jest
'' .spyOn(fileHelper, 'writeObjectsToFile')
]) .mockImplementationOnce(() => [''])
}) })
afterEach(() => { afterEach(() => {
vi.restoreAllMocks() jest.restoreAllMocks()
}) })
test('routeBlueGreenService with timeout', async () => { test('routeBlueGreenService with timeout', async () => {
const timeout = '240s' const timeout = '240s'
// Mock deployObjects to capture timeout parameter // Mock deployObjects to capture timeout parameter
const deployObjectsSpy = vi const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects') .spyOn(require('./blueGreenHelper'), 'deployObjects')
.mockResolvedValue({ .mockResolvedValue({
execResult: mockSuccessResult, execResult: mockSuccessResult,
manifestFiles: [] manifestFiles: []
@@ -239,29 +237,23 @@ describe('route timeout tests', () => {
test('routeBlueGreenSMI with timeout', async () => { test('routeBlueGreenSMI with timeout', async () => {
const timeout = '300s' const timeout = '300s'
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(() => jest
Promise.resolve('v1alpha3') .spyOn(TSutils, 'getTrafficSplitAPIVersion')
) .mockImplementation(() => Promise.resolve('v1alpha3'))
// Mock deployObjects and createTrafficSplitObject to capture timeout parameter // Mock deployObjects and createTrafficSplitObject to capture timeout parameter
const deployObjectsSpy = vi const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects') .spyOn(require('./blueGreenHelper'), 'deployObjects')
.mockResolvedValue({ .mockResolvedValue({
execResult: mockSuccessResult, execResult: mockSuccessResult,
manifestFiles: [] manifestFiles: []
}) })
const createTrafficSplitSpy = vi const createTrafficSplitSpy = jest
.spyOn(smiHelper, 'createTrafficSplitObject') .spyOn(require('./smiBlueGreenHelper'), 'createTrafficSplitObject')
.mockResolvedValue({ .mockResolvedValue({
apiVersion: 'split.smi-spec.io/v1alpha3', metadata: {name: 'nginx-service-trafficsplit'},
kind: 'TrafficSplit', spec: {backends: []}
metadata: {
name: 'nginx-service-trafficsplit',
labels: new Map(),
annotations: new Map()
},
spec: {service: 'nginx-service', backends: []}
}) })
const value = await routeBlueGreenSMI( const value = await routeBlueGreenSMI(
@@ -292,8 +284,8 @@ describe('route timeout tests', () => {
const timeout = '180s' const timeout = '180s'
// Mock deployObjects to capture timeout parameter // Mock deployObjects to capture timeout parameter
const deployObjectsSpy = vi const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects') .spyOn(require('./blueGreenHelper'), 'deployObjects')
.mockResolvedValue({ .mockResolvedValue({
execResult: mockSuccessResult, execResult: mockSuccessResult,
manifestFiles: [] manifestFiles: []
@@ -317,8 +309,8 @@ describe('route timeout tests', () => {
}) })
test('route functions without timeout should pass undefined', async () => { test('route functions without timeout should pass undefined', async () => {
const deployObjectsSpy = vi const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects') .spyOn(require('./blueGreenHelper'), 'deployObjects')
.mockResolvedValue({ .mockResolvedValue({
execResult: mockSuccessResult, execResult: mockSuccessResult,
manifestFiles: [] manifestFiles: []
+10 -10
View File
@@ -1,26 +1,26 @@
import {sleep} from '../../utilities/timeUtils.js' import {sleep} from '../../utilities/timeUtils'
import {RouteStrategy} from '../../types/routeStrategy.js' import {RouteStrategy} from '../../types/routeStrategy'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import { import {
BlueGreenDeployment, BlueGreenDeployment,
BlueGreenManifests BlueGreenManifests
} from '../../types/blueGreenTypes.js' } from '../../types/blueGreenTypes'
import { import {
getManifestObjects, getManifestObjects,
GREEN_LABEL_VALUE, GREEN_LABEL_VALUE,
deployObjects deployObjects
} from './blueGreenHelper.js' } from './blueGreenHelper'
import { import {
getUpdatedBlueGreenIngress, getUpdatedBlueGreenIngress,
isIngressRouted isIngressRouted
} from './ingressBlueGreenHelper.js' } from './ingressBlueGreenHelper'
import {getUpdatedBlueGreenService} from './serviceBlueGreenHelper.js' import {getUpdatedBlueGreenService} from './serviceBlueGreenHelper'
import {createTrafficSplitObject} from './smiBlueGreenHelper.js' import {createTrafficSplitObject} from './smiBlueGreenHelper'
import * as core from '@actions/core' import * as core from '@actions/core'
import {K8sObject, TrafficSplitObject} from '../../types/k8sObject.js' import {K8sObject, TrafficSplitObject} from '../../types/k8sObject'
import {getBufferTime} from '../../inputUtils.js' import {getBufferTime} from '../../inputUtils'
export async function routeBlueGreenForDeploy( export async function routeBlueGreenForDeploy(
kubectl: Kubectl, kubectl: Kubectl,
@@ -1,26 +1,26 @@
import {vi} from 'vitest'
import * as core from '@actions/core' import * as core from '@actions/core'
import { import {
BLUE_GREEN_VERSION_LABEL, BLUE_GREEN_VERSION_LABEL,
getManifestObjects, getManifestObjects,
GREEN_LABEL_VALUE GREEN_LABEL_VALUE
} from './blueGreenHelper.js' } from './blueGreenHelper'
import * as bgHelper from './blueGreenHelper.js' import * as bgHelper from './blueGreenHelper'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import { import {
getServiceSpecLabel, getServiceSpecLabel,
getUpdatedBlueGreenService, getUpdatedBlueGreenService,
validateServicesState validateServicesState
} from './serviceBlueGreenHelper.js' } from './serviceBlueGreenHelper'
let testObjects let testObjects
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml'] const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
vi.mock('../../types/kubectl') jest.mock('../../types/kubectl')
const kubectl = new Kubectl('') const kubectl = new Kubectl('')
describe('blue/green service helper tests', () => { describe('blue/green service helper tests', () => {
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() //@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath) testObjects = getManifestObjects(ingressFilepath)
}) })
@@ -42,7 +42,7 @@ describe('blue/green service helper tests', () => {
mockLabels[BLUE_GREEN_VERSION_LABEL] = bgHelper.GREEN_LABEL_VALUE mockLabels[BLUE_GREEN_VERSION_LABEL] = bgHelper.GREEN_LABEL_VALUE
const mockSelectors = new Map<string, string>() const mockSelectors = new Map<string, string>()
mockSelectors[BLUE_GREEN_VERSION_LABEL] = GREEN_LABEL_VALUE mockSelectors[BLUE_GREEN_VERSION_LABEL] = GREEN_LABEL_VALUE
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() => jest.spyOn(bgHelper, 'fetchResource').mockImplementation(() =>
Promise.resolve({ Promise.resolve({
kind: 'Service', kind: 'Service',
spec: {selector: mockSelectors}, spec: {selector: mockSelectors},
@@ -1,12 +1,12 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {K8sServiceObject} from '../../types/k8sObject.js' import {K8sServiceObject} from '../../types/k8sObject'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import { import {
addBlueGreenLabelsAndAnnotations, addBlueGreenLabelsAndAnnotations,
BLUE_GREEN_VERSION_LABEL, BLUE_GREEN_VERSION_LABEL,
fetchResource, fetchResource,
GREEN_LABEL_VALUE GREEN_LABEL_VALUE
} from './blueGreenHelper.js' } from './blueGreenHelper'
// add green labels to configure existing service // add green labels to configure existing service
export function getUpdatedBlueGreenService( export function getUpdatedBlueGreenService(
@@ -1,16 +1,15 @@
import {vi} from 'vitest' import {TrafficSplitObject} from '../../types/k8sObject'
import {TrafficSplitObject} from '../../types/k8sObject.js' import {Kubectl} from '../../types/kubectl'
import {Kubectl} from '../../types/kubectl.js' import * as fileHelper from '../../utilities/fileUtils'
import * as fileHelper from '../../utilities/fileUtils.js' import * as TSutils from '../../utilities/trafficSplitUtils'
import * as TSutils from '../../utilities/trafficSplitUtils.js'
import {BlueGreenManifests} from '../../types/blueGreenTypes.js' import {BlueGreenManifests} from '../../types/blueGreenTypes'
import { import {
BLUE_GREEN_VERSION_LABEL, BLUE_GREEN_VERSION_LABEL,
getManifestObjects, getManifestObjects,
GREEN_LABEL_VALUE, GREEN_LABEL_VALUE,
NONE_LABEL_VALUE NONE_LABEL_VALUE
} from './blueGreenHelper.js' } from './blueGreenHelper'
import { import {
cleanupSMI, cleanupSMI,
@@ -22,10 +21,10 @@ import {
setupSMI, setupSMI,
TRAFFIC_SPLIT_OBJECT, TRAFFIC_SPLIT_OBJECT,
validateTrafficSplitsState validateTrafficSplitsState
} from './smiBlueGreenHelper.js' } from './smiBlueGreenHelper'
import * as bgHelper from './blueGreenHelper.js' import * as bgHelper from './blueGreenHelper'
vi.mock('../../types/kubectl') jest.mock('../../types/kubectl')
const kc = new Kubectl('') const kc = new Kubectl('')
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml'] const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
@@ -69,20 +68,21 @@ const mockTsObject: TrafficSplitObject = {
describe('SMI Helper tests', () => { describe('SMI Helper tests', () => {
let testObjects: BlueGreenManifests let testObjects: BlueGreenManifests
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() //@ts-ignore
Kubectl.mockClear()
vi.spyOn(TSutils, 'getTrafficSplitAPIVersion').mockImplementation(() => jest
Promise.resolve('') .spyOn(TSutils, 'getTrafficSplitAPIVersion')
) .mockImplementation(() => Promise.resolve(''))
testObjects = getManifestObjects(ingressFilepath) testObjects = getManifestObjects(ingressFilepath)
vi.spyOn(fileHelper, 'writeObjectsToFile').mockImplementationOnce(() => [ jest
'' .spyOn(fileHelper, 'writeObjectsToFile')
]) .mockImplementationOnce(() => [''])
}) })
test('setupSMI tests', async () => { test('setupSMI tests', async () => {
vi.spyOn(kc, 'apply').mockResolvedValue(mockSuccessResult) jest.spyOn(kc, 'apply').mockResolvedValue(mockSuccessResult)
const smiResults = await setupSMI(kc, testObjects.serviceEntityList) const smiResults = await setupSMI(kc, testObjects.serviceEntityList)
@@ -174,9 +174,9 @@ describe('SMI Helper tests', () => {
}) })
test('validateTrafficSplitsState', async () => { test('validateTrafficSplitsState', async () => {
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() => jest
Promise.resolve(mockTsObject) .spyOn(bgHelper, 'fetchResource')
) .mockImplementation(() => Promise.resolve(mockTsObject))
let valResult = await validateTrafficSplitsState( let valResult = await validateTrafficSplitsState(
kc, kc,
@@ -187,9 +187,9 @@ describe('SMI Helper tests', () => {
const mockTsCopy = JSON.parse(JSON.stringify(mockTsObject)) const mockTsCopy = JSON.parse(JSON.stringify(mockTsObject))
mockTsCopy.spec.backends[0].weight = MAX_VAL mockTsCopy.spec.backends[0].weight = MAX_VAL
vi.spyOn(bgHelper, 'fetchResource').mockImplementation(() => jest
Promise.resolve(mockTsCopy) .spyOn(bgHelper, 'fetchResource')
) .mockImplementation(() => Promise.resolve(mockTsCopy))
valResult = await validateTrafficSplitsState( valResult = await validateTrafficSplitsState(
kc, kc,
@@ -197,7 +197,7 @@ describe('SMI Helper tests', () => {
) )
expect(valResult).toBe(false) expect(valResult).toBe(false)
vi.spyOn(bgHelper, 'fetchResource').mockResolvedValue(null) jest.spyOn(bgHelper, 'fetchResource').mockImplementation()
valResult = await validateTrafficSplitsState( valResult = await validateTrafficSplitsState(
kc, kc,
testObjects.serviceEntityList testObjects.serviceEntityList
@@ -218,7 +218,7 @@ describe('SMI Helper tests', () => {
name: 'should throw error when kubectl apply fails during SMI setup', name: 'should throw error when kubectl apply fails during SMI setup',
fn: () => setupSMI(kc, testObjects.serviceEntityList), fn: () => setupSMI(kc, testObjects.serviceEntityList),
setup: () => { setup: () => {
vi.spyOn(kc, 'apply').mockResolvedValue(mockFailureResult) jest.spyOn(kc, 'apply').mockResolvedValue(mockFailureResult)
} }
} }
])('$name', async ({fn, setup}) => { ])('$name', async ({fn, setup}) => {
@@ -229,7 +229,7 @@ describe('SMI Helper tests', () => {
// Timeout-specific tests // Timeout-specific tests
test('setupSMI with timeout test', async () => { test('setupSMI with timeout test', async () => {
const deployObjectsSpy = vi const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects') .spyOn(bgHelper, 'deployObjects')
.mockResolvedValue({ .mockResolvedValue({
execResult: mockSuccessResult, execResult: mockSuccessResult,
@@ -257,7 +257,7 @@ describe('SMI Helper tests', () => {
}) })
test('createTrafficSplitObject with timeout test', async () => { test('createTrafficSplitObject with timeout test', async () => {
const deleteObjectsSpy = vi const deleteObjectsSpy = jest
.spyOn(bgHelper, 'deleteObjects') .spyOn(bgHelper, 'deleteObjects')
.mockResolvedValue() .mockResolvedValue()
@@ -288,7 +288,7 @@ describe('SMI Helper tests', () => {
}) })
test('createTrafficSplitObject with GREEN_LABEL_VALUE and timeout test', async () => { test('createTrafficSplitObject with GREEN_LABEL_VALUE and timeout test', async () => {
const deleteObjectsSpy = vi const deleteObjectsSpy = jest
.spyOn(bgHelper, 'deleteObjects') .spyOn(bgHelper, 'deleteObjects')
.mockResolvedValue() .mockResolvedValue()
@@ -321,7 +321,7 @@ describe('SMI Helper tests', () => {
}) })
test('cleanupSMI with timeout test', async () => { test('cleanupSMI with timeout test', async () => {
const deleteObjectsSpy = vi const deleteObjectsSpy = jest
.spyOn(bgHelper, 'deleteObjects') .spyOn(bgHelper, 'deleteObjects')
.mockResolvedValue() .mockResolvedValue()
@@ -352,7 +352,7 @@ describe('SMI Helper tests', () => {
}) })
test('setupSMI without timeout test', async () => { test('setupSMI without timeout test', async () => {
const deployObjectsSpy = vi const deployObjectsSpy = jest
.spyOn(bgHelper, 'deployObjects') .spyOn(bgHelper, 'deployObjects')
.mockResolvedValue({ .mockResolvedValue({
execResult: mockSuccessResult, execResult: mockSuccessResult,
@@ -375,7 +375,7 @@ describe('SMI Helper tests', () => {
}) })
test('createTrafficSplitObject without timeout test', async () => { test('createTrafficSplitObject without timeout test', async () => {
const deleteObjectsSpy = vi const deleteObjectsSpy = jest
.spyOn(bgHelper, 'deleteObjects') .spyOn(bgHelper, 'deleteObjects')
.mockResolvedValue() .mockResolvedValue()
@@ -398,7 +398,7 @@ describe('SMI Helper tests', () => {
}) })
test('cleanupSMI without timeout test', async () => { test('cleanupSMI without timeout test', async () => {
const deleteObjectsSpy = vi const deleteObjectsSpy = jest
.spyOn(bgHelper, 'deleteObjects') .spyOn(bgHelper, 'deleteObjects')
.mockResolvedValue() .mockResolvedValue()
@@ -1,6 +1,6 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import * as kubectlUtils from '../../utilities/trafficSplitUtils.js' import * as kubectlUtils from '../../utilities/trafficSplitUtils'
import { import {
deleteObjects, deleteObjects,
deployObjects, deployObjects,
@@ -11,15 +11,15 @@ import {
GREEN_SUFFIX, GREEN_SUFFIX,
NONE_LABEL_VALUE, NONE_LABEL_VALUE,
STABLE_SUFFIX STABLE_SUFFIX
} from './blueGreenHelper.js' } from './blueGreenHelper'
import {BlueGreenDeployment} from '../../types/blueGreenTypes.js' import {BlueGreenDeployment} from '../../types/blueGreenTypes'
import { import {
K8sDeleteObject, K8sDeleteObject,
K8sObject, K8sObject,
TrafficSplitObject TrafficSplitObject
} from '../../types/k8sObject.js' } from '../../types/k8sObject'
import {DeployResult} from '../../types/deployResult.js' import {DeployResult} from '../../types/deployResult'
import {inputAnnotations} from '../../inputUtils.js' import {inputAnnotations} from '../../inputUtils'
export const TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX = '-trafficsplit' export const TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX = '-trafficsplit'
export const TRAFFIC_SPLIT_OBJECT = '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 fs from 'fs'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import * as core from '@actions/core' import * as core from '@actions/core'
@@ -7,15 +7,15 @@ import {
isDeploymentEntity, isDeploymentEntity,
isServiceEntity, isServiceEntity,
KubernetesWorkload KubernetesWorkload
} from '../../types/kubernetesTypes.js' } from '../../types/kubernetesTypes'
import * as utils from '../../utilities/manifestUpdateUtils.js' import * as utils from '../../utilities/manifestUpdateUtils'
import { import {
updateObjectAnnotations, updateObjectAnnotations,
updateObjectLabels, updateObjectLabels,
updateSelectorLabels updateSelectorLabels
} from '../../utilities/manifestUpdateUtils.js' } from '../../utilities/manifestUpdateUtils'
import {updateSpecLabels} from '../../utilities/manifestSpecLabelUtils.js' import {updateSpecLabels} from '../../utilities/manifestSpecLabelUtils'
import {checkForErrors} from '../../utilities/kubectlUtils.js' import {checkForErrors} from '../../utilities/kubectlUtils'
export const CANARY_VERSION_LABEL = 'workflow/version' export const CANARY_VERSION_LABEL = 'workflow/version'
const BASELINE_SUFFIX = '-baseline' 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 * as core from '@actions/core'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import { import {
deployPodCanary, deployPodCanary,
calculateReplicaCountForCanary calculateReplicaCountForCanary
} from './podCanaryHelper.js' } from './podCanaryHelper'
vi.mock('../../types/kubectl') jest.mock('../../types/kubectl')
const kc = new Kubectl('') const kc = new Kubectl('')
@@ -39,17 +35,18 @@ const TIMEOUT_300S = '300s'
describe('Pod Canary Helper tests', () => { describe('Pod Canary Helper tests', () => {
let mockFilePaths: string[] let mockFilePaths: string[]
let kubectlApplySpy: MockInstance let kubectlApplySpy: jest.SpyInstance
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() //@ts-ignore
vi.restoreAllMocks() Kubectl.mockClear()
jest.restoreAllMocks()
mockFilePaths = testManifestFiles mockFilePaths = testManifestFiles
kubectlApplySpy = vi.spyOn(kc, 'apply') kubectlApplySpy = jest.spyOn(kc, 'apply')
// Mock core.getInput with default values // Mock core.getInput with default values
vi.spyOn(core, 'getInput').mockImplementation((name: string) => { jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) { switch (name) {
case 'percentage': case 'percentage':
return VALID_PERCENTAGE.toString() return VALID_PERCENTAGE.toString()
@@ -64,7 +61,7 @@ describe('Pod Canary Helper tests', () => {
}) })
afterEach(() => { afterEach(() => {
vi.restoreAllMocks() jest.restoreAllMocks()
kubectlApplySpy.mockClear() kubectlApplySpy.mockClear()
}) })
@@ -117,7 +114,7 @@ describe('Pod Canary Helper tests', () => {
}) })
test('should throw error for invalid low percentage', async () => { 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() if (name === 'percentage') return INVALID_LOW_PERCENTAGE.toString()
return '' return ''
}) })
@@ -130,7 +127,7 @@ describe('Pod Canary Helper tests', () => {
}) })
test('should throw error for invalid high percentage', async () => { 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() if (name === 'percentage') return INVALID_HIGH_PERCENTAGE.toString()
return '' return ''
}) })
@@ -146,7 +143,7 @@ describe('Pod Canary Helper tests', () => {
kubectlApplySpy.mockResolvedValue(mockSuccessResult) kubectlApplySpy.mockResolvedValue(mockSuccessResult)
// Test minimum valid percentage // 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() if (name === 'percentage') return MIN_PERCENTAGE.toString()
return '' return ''
}) })
@@ -155,7 +152,7 @@ describe('Pod Canary Helper tests', () => {
expect(resultMin.execResult).toEqual(mockSuccessResult) expect(resultMin.execResult).toEqual(mockSuccessResult)
// Test maximum valid percentage // 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() if (name === 'percentage') return MAX_PERCENTAGE.toString()
return '' return ''
}) })
@@ -165,7 +162,7 @@ describe('Pod Canary Helper tests', () => {
}) })
test('should handle force deployment option', async () => { test('should handle force deployment option', async () => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => { jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) { switch (name) {
case 'percentage': case 'percentage':
return VALID_PERCENTAGE.toString() return VALID_PERCENTAGE.toString()
@@ -189,7 +186,7 @@ describe('Pod Canary Helper tests', () => {
}) })
test('should handle server-side apply option', async () => { test('should handle server-side apply option', async () => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => { jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) { switch (name) {
case 'percentage': case 'percentage':
return VALID_PERCENTAGE.toString() 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 core from '@actions/core'
import * as fs from 'fs' import * as fs from 'fs'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import * as fileHelper from '../../utilities/fileUtils.js' import * as fileHelper from '../../utilities/fileUtils'
import * as canaryDeploymentHelper from './canaryHelper.js' import * as canaryDeploymentHelper from './canaryHelper'
import {isDeploymentEntity} from '../../types/kubernetesTypes.js' import {isDeploymentEntity} from '../../types/kubernetesTypes'
import {getReplicaCount} from '../../utilities/manifestUpdateUtils.js' import {getReplicaCount} from '../../utilities/manifestUpdateUtils'
import {DeployResult} from '../../types/deployResult.js' import {DeployResult} from '../../types/deployResult'
import {K8sObject} from '../../types/k8sObject.js' import {K8sObject} from '../../types/k8sObject'
import {checkForErrors} from '../../utilities/kubectlUtils.js' import {checkForErrors} from '../../utilities/kubectlUtils'
export async function deployPodCanary( export async function deployPodCanary(
filePaths: string[], 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 core from '@actions/core'
import * as fs from 'fs' import * as fs from 'fs'
import {Kubectl} from '../../types/kubectl.js' import {Kubectl} from '../../types/kubectl'
import { import {
deploySMICanary, deploySMICanary,
redirectTrafficToCanaryDeployment, redirectTrafficToCanaryDeployment,
redirectTrafficToStableDeployment redirectTrafficToStableDeployment
} from './smiCanaryHelper.js' } from './smiCanaryHelper'
vi.mock('../../types/kubectl') jest.mock('../../types/kubectl')
const kc = new Kubectl('') const kc = new Kubectl('')
@@ -61,21 +40,22 @@ const TIMEOUT_240S = '240s'
describe('SMI Canary Helper tests', () => { describe('SMI Canary Helper tests', () => {
let mockFilePaths: string[] let mockFilePaths: string[]
let kubectlApplySpy: MockInstance let kubectlApplySpy: jest.SpyInstance
let kubectlExecuteCommandSpy: MockInstance let kubectlExecuteCommandSpy: jest.SpyInstance
beforeEach(() => { beforeEach(() => {
vi.mocked(Kubectl).mockClear() //@ts-ignore
vi.restoreAllMocks() Kubectl.mockClear()
jest.restoreAllMocks()
mockFilePaths = testManifestFiles mockFilePaths = testManifestFiles
kubectlApplySpy = vi.spyOn(kc, 'apply') kubectlApplySpy = jest.spyOn(kc, 'apply')
kubectlExecuteCommandSpy = vi kubectlExecuteCommandSpy = jest
.spyOn(kc, 'executeCommand') .spyOn(kc, 'executeCommand')
.mockResolvedValue(mockExecuteCommandResult) .mockResolvedValue(mockExecuteCommandResult)
// Mock core.getInput with default values // Mock core.getInput with default values
vi.spyOn(core, 'getInput').mockImplementation((name: string) => { jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) { switch (name) {
case 'percentage': case 'percentage':
return '50' return '50'
@@ -92,7 +72,7 @@ describe('SMI Canary Helper tests', () => {
}) })
afterEach(() => { afterEach(() => {
vi.restoreAllMocks() jest.restoreAllMocks()
kubectlApplySpy.mockClear() kubectlApplySpy.mockClear()
}) })
@@ -126,7 +106,7 @@ describe('SMI Canary Helper tests', () => {
}) })
test('should handle custom replica count from input', async () => { 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) { switch (name) {
case 'baseline-and-canary-replicas': case 'baseline-and-canary-replicas':
return VALID_REPLICA_COUNT.toString() 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 core from '@actions/core'
import * as fs from 'fs' import * as fs from 'fs'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import * as fileHelper from '../../utilities/fileUtils.js' import * as fileHelper from '../../utilities/fileUtils'
import * as kubectlUtils from '../../utilities/trafficSplitUtils.js' import * as kubectlUtils from '../../utilities/trafficSplitUtils'
import * as canaryDeploymentHelper from './canaryHelper.js' import * as canaryDeploymentHelper from './canaryHelper'
import * as podCanaryHelper from './podCanaryHelper.js' import * as podCanaryHelper from './podCanaryHelper'
import { import {isDeploymentEntity, isServiceEntity} from '../../types/kubernetesTypes'
isDeploymentEntity, import {checkForErrors} from '../../utilities/kubectlUtils'
isServiceEntity import {inputAnnotations} from '../../inputUtils'
} from '../../types/kubernetesTypes.js' import {DeployResult} from '../../types/deployResult'
import {checkForErrors} from '../../utilities/kubectlUtils.js' import {K8sObject} from '../../types/k8sObject'
import {inputAnnotations} from '../../inputUtils.js'
import {DeployResult} from '../../types/deployResult.js'
import {K8sObject} from '../../types/k8sObject.js'
const TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX = '-workflow-rollout' const TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX = '-workflow-rollout'
const TRAFFIC_SPLIT_OBJECT = 'TrafficSplit' const TRAFFIC_SPLIT_OBJECT = 'TrafficSplit'
+19 -19
View File
@@ -1,41 +1,41 @@
import * as fs from 'fs' import * as fs from 'fs'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import * as canaryDeploymentHelper from './canary/canaryHelper.js' import * as canaryDeploymentHelper from './canary/canaryHelper'
import * as models from '../types/kubernetesTypes.js' import * as models from '../types/kubernetesTypes'
import {isDeploymentEntity} from '../types/kubernetesTypes.js' import {isDeploymentEntity} from '../types/kubernetesTypes'
import * as fileHelper from '../utilities/fileUtils.js' import * as fileHelper from '../utilities/fileUtils'
import * as KubernetesManifestUtility from '../utilities/manifestStabilityUtils.js' import * as KubernetesManifestUtility from '../utilities/manifestStabilityUtils'
import {Kubectl, Resource} from '../types/kubectl.js' import {Kubectl, Resource} from '../types/kubectl'
import {deployPodCanary} from './canary/podCanaryHelper.js' import {deployPodCanary} from './canary/podCanaryHelper'
import {deploySMICanary} from './canary/smiCanaryHelper.js' import {deploySMICanary} from './canary/smiCanaryHelper'
import {DeploymentConfig} from '../types/deploymentConfig.js' import {DeploymentConfig} from '../types/deploymentConfig'
import {deployBlueGreen} from './blueGreen/deploy.js' import {deployBlueGreen} from './blueGreen/deploy'
import {DeploymentStrategy} from '../types/deploymentStrategy.js' import {DeploymentStrategy} from '../types/deploymentStrategy'
import * as core from '@actions/core' import * as core from '@actions/core'
import { import {
parseTrafficSplitMethod, parseTrafficSplitMethod,
TrafficSplitMethod TrafficSplitMethod
} from '../types/trafficSplitMethod.js' } from '../types/trafficSplitMethod'
import {parseRouteStrategy} from '../types/routeStrategy.js' import {parseRouteStrategy} from '../types/routeStrategy'
import {ExecOutput} from '@actions/exec' import {ExecOutput} from '@actions/exec'
import { import {
getWorkflowAnnotationKeyLabel, getWorkflowAnnotationKeyLabel,
getWorkflowAnnotations, getWorkflowAnnotations,
cleanLabel cleanLabel
} from '../utilities/workflowAnnotationUtils.js' } from '../utilities/workflowAnnotationUtils'
import { import {
annotateChildPods, annotateChildPods,
checkForErrors, checkForErrors,
getLastSuccessfulRunSha getLastSuccessfulRunSha
} from '../utilities/kubectlUtils.js' } from '../utilities/kubectlUtils'
import { import {
getWorkflowFilePath, getWorkflowFilePath,
normalizeWorkflowStrLabel normalizeWorkflowStrLabel
} from '../utilities/githubUtils.js' } from '../utilities/githubUtils'
import {getDeploymentConfig} from '../utilities/dockerUtils.js' import {getDeploymentConfig} from '../utilities/dockerUtils'
import {DeployResult} from '../types/deployResult.js' import {DeployResult} from '../types/deployResult'
import {ClusterType} from '../inputUtils.js' import {ClusterType} from '../inputUtils'
export async function deployManifests( export async function deployManifests(
files: string[], files: string[],
+1 -1
View File
@@ -1,4 +1,4 @@
import {Action, parseAction} from './action.js' import {Action, parseAction} from './action'
describe('Action type', () => { describe('Action type', () => {
test('it has required values', () => { test('it has required values', () => {
+2 -2
View File
@@ -1,5 +1,5 @@
import {DeployResult} from './deployResult.js' import {DeployResult} from './deployResult'
import {K8sObject, K8sDeleteObject} from './k8sObject.js' import {K8sObject, K8sDeleteObject} from './k8sObject'
export interface BlueGreenDeployment { export interface BlueGreenDeployment {
deployResult: DeployResult deployResult: DeployResult
+1 -4
View File
@@ -1,7 +1,4 @@
import { import {DeploymentStrategy, parseDeploymentStrategy} from './deploymentStrategy'
DeploymentStrategy,
parseDeploymentStrategy
} from './deploymentStrategy.js'
describe('Deployment strategy type', () => { describe('Deployment strategy type', () => {
test('it has required values', () => { test('it has required values', () => {
+4 -7
View File
@@ -1,7 +1,4 @@
import {vi} from 'vitest' import {DockerExec} from './docker'
vi.mock('@actions/exec')
import {DockerExec} from './docker.js'
import * as actions from '@actions/exec' import * as actions from '@actions/exec'
const dockerPath = 'dockerPath' const dockerPath = 'dockerPath'
@@ -15,7 +12,7 @@ describe('Docker class', () => {
const execReturn = {exitCode: 0, stdout: 'Output', stderr: ''} const execReturn = {exitCode: 0, stdout: 'Output', stderr: ''}
beforeEach(() => { beforeEach(() => {
vi.spyOn(actions, 'getExecOutput').mockImplementation(async () => { jest.spyOn(actions, 'getExecOutput').mockImplementation(async () => {
return execReturn return execReturn
}) })
}) })
@@ -63,7 +60,7 @@ describe('Docker class', () => {
const execReturn = {exitCode: 3, stdout: '', stderr: ''} const execReturn = {exitCode: 3, stdout: '', stderr: ''}
beforeEach(() => { beforeEach(() => {
vi.spyOn(actions, 'getExecOutput').mockImplementation(async () => { jest.spyOn(actions, 'getExecOutput').mockImplementation(async () => {
return execReturn return execReturn
}) })
}) })
@@ -83,7 +80,7 @@ describe('Docker class', () => {
const execReturn = {exitCode: 0, stdout: '', stderr: 'Output'} const execReturn = {exitCode: 0, stdout: '', stderr: 'Output'}
beforeEach(() => { beforeEach(() => {
vi.spyOn(actions, 'getExecOutput').mockImplementation(async () => { jest.spyOn(actions, 'getExecOutput').mockImplementation(async () => {
return execReturn return execReturn
}) })
}) })
+12 -18
View File
@@ -1,10 +1,4 @@
import {vi} from 'vitest' import {getKubectlPath, Kubectl} from './kubectl'
vi.mock('@actions/exec')
vi.mock('@actions/io')
vi.mock('@actions/core')
vi.mock('@actions/tool-cache')
import {getKubectlPath, Kubectl} from './kubectl.js'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import * as io from '@actions/io' import * as io from '@actions/io'
import * as core from '@actions/core' import * as core from '@actions/core'
@@ -15,27 +9,27 @@ describe('Kubectl path', () => {
const path = 'path' const path = 'path'
it('gets the kubectl path', async () => { it('gets the kubectl path', async () => {
vi.spyOn(core, 'getInput').mockImplementationOnce(() => '') jest.spyOn(core, 'getInput').mockImplementationOnce(() => '')
vi.spyOn(io, 'which').mockImplementationOnce(async () => path) jest.spyOn(io, 'which').mockImplementationOnce(async () => path)
expect(await getKubectlPath()).toBe(path) expect(await getKubectlPath()).toBe(path)
}) })
it('gets the kubectl path with version', async () => { it('gets the kubectl path with version', async () => {
vi.spyOn(core, 'getInput').mockImplementationOnce(() => version) jest.spyOn(core, 'getInput').mockImplementationOnce(() => version)
vi.spyOn(toolCache, 'find').mockImplementationOnce(() => path) jest.spyOn(toolCache, 'find').mockImplementationOnce(() => path)
expect(await getKubectlPath()).toBe(path) expect(await getKubectlPath()).toBe(path)
}) })
it('throws if kubectl not found', async () => { it('throws if kubectl not found', async () => {
// without version // without version
vi.spyOn(io, 'which').mockImplementationOnce(async () => '') jest.spyOn(io, 'which').mockImplementationOnce(async () => '')
await expect(() => getKubectlPath()).rejects.toThrow() await expect(() => getKubectlPath()).rejects.toThrow()
// with verision // with verision
vi.spyOn(core, 'getInput').mockImplementationOnce(() => '') jest.spyOn(core, 'getInput').mockImplementationOnce(() => '')
vi.spyOn(io, 'which').mockImplementationOnce(async () => '') jest.spyOn(io, 'which').mockImplementationOnce(async () => '')
await expect(() => getKubectlPath()).rejects.toThrow() await expect(() => getKubectlPath()).rejects.toThrow()
}) })
}) })
@@ -52,7 +46,7 @@ describe('Kubectl class', () => {
const mockExecReturn = {exitCode: 0, stdout: 'Output', stderr: ''} const mockExecReturn = {exitCode: 0, stdout: 'Output', stderr: ''}
beforeEach(() => { beforeEach(() => {
vi.spyOn(exec, 'getExecOutput').mockImplementation(async () => { jest.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
return mockExecReturn return mockExecReturn
}) })
}) })
@@ -643,7 +637,7 @@ describe('Kubectl class', () => {
stderr: '' stderr: ''
} }
vi.spyOn(exec, 'getExecOutput').mockImplementationOnce(async () => { jest.spyOn(exec, 'getExecOutput').mockImplementationOnce(async () => {
return describeReturn return describeReturn
}) })
@@ -656,7 +650,7 @@ describe('Kubectl class', () => {
const skipTls = true const skipTls = true
const kubectl = new Kubectl(kubectlPath, testNamespace, skipTls) const kubectl = new Kubectl(kubectlPath, testNamespace, skipTls)
vi.spyOn(exec, 'getExecOutput').mockImplementation(async () => { jest.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
return {exitCode: 0, stderr: '', stdout: ''} return {exitCode: 0, stderr: '', stdout: ''}
}) })
@@ -683,7 +677,7 @@ describe('Kubectl namespace handling', () => {
const execReturn = {exitCode: 0, stdout: 'Output', stderr: ''} const execReturn = {exitCode: 0, stdout: 'Output', stderr: ''}
beforeEach(() => { beforeEach(() => {
vi.spyOn(exec, 'getExecOutput').mockResolvedValue(execReturn) jest.spyOn(exec, 'getExecOutput').mockResolvedValue(execReturn)
}) })
const runApply = async (namespace?: string) => { const runApply = async (namespace?: string) => {
+2 -6
View File
@@ -1,5 +1,5 @@
import {ExecOutput, getExecOutput} from '@actions/exec' 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 core from '@actions/core'
import * as toolCache from '@actions/tool-cache' import * as toolCache from '@actions/tool-cache'
import * as io from '@actions/io' 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[] { protected getFlags(namespaceOverride?: string): string[] {
const flags = [] const flags = []
if (this.ignoreSSLErrors) { if (this.ignoreSSLErrors) {
flags.push('--insecure-skip-tls-verify') flags.push('--insecure-skip-tls-verify')
} }
const ns = this.getNamespace(namespaceOverride) const ns = namespaceOverride || this.namespace
if (ns) { if (ns) {
flags.push('--namespace', ns) flags.push('--namespace', ns)
} }
+1 -1
View File
@@ -10,7 +10,7 @@ import {
ServiceTypes, ServiceTypes,
WORKLOAD_TYPES, WORKLOAD_TYPES,
WORKLOAD_TYPES_WITH_ROLLOUT_STATUS WORKLOAD_TYPES_WITH_ROLLOUT_STATUS
} from './kubernetesTypes.js' } from './kubernetesTypes'
describe('Kubernetes types', () => { describe('Kubernetes types', () => {
it('contains kubernetes workloads', () => { it('contains kubernetes workloads', () => {
+6 -9
View File
@@ -1,13 +1,10 @@
import {vi} from 'vitest' import * as fileUtils from '../utilities/fileUtils'
vi.mock('@actions/exec')
import * as fileUtils from '../utilities/fileUtils.js'
import fs from 'node:fs' import fs from 'node:fs'
import { import {
PrivateKubectl, PrivateKubectl,
extractFileNames, extractFileNames,
replaceFileNamesWithShallowNamesRelativeToTemp replaceFileNamesWithShallowNamesRelativeToTemp
} from './privatekubectl.js' } from './privatekubectl'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
describe('Private kubectl', () => { describe('Private kubectl', () => {
@@ -20,14 +17,14 @@ describe('Private kubectl', () => {
'resourceName' 'resourceName'
) )
const spy = vi const spy = jest
.spyOn(fileUtils, 'getTempDirectory') .spyOn(fileUtils, 'getTempDirectory')
.mockImplementation(() => { .mockImplementation(() => {
return '/tmp' return '/tmp'
}) })
vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {}) jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
vi.spyOn(fs, 'readFileSync').mockImplementation((filename) => { jest.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
return 'test contents' return 'test contents'
}) })
@@ -51,7 +48,7 @@ describe('Private kubectl', () => {
test('Should throw well defined Error on error from Azure', async () => { test('Should throw well defined Error on error from Azure', async () => {
const errorMsg = 'An error message' const errorMsg = 'An error message'
vi.spyOn(exec, 'getExecOutput').mockImplementation(async () => { jest.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
return {exitCode: 1, stdout: '', stderr: errorMsg} 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 minimist from 'minimist'
import {ExecOptions, ExecOutput, getExecOutput} from '@actions/exec' import {ExecOptions, ExecOutput, getExecOutput} from '@actions/exec'
import * as core from '@actions/core' import * as core from '@actions/core'
import fs from 'node:fs' import fs from 'node:fs'
import * as path from 'path' import * as path from 'path'
import {getTempDirectory} from '../utilities/fileUtils.js' import {getTempDirectory} from '../utilities/fileUtils'
export class PrivateKubectl extends Kubectl { export class PrivateKubectl extends Kubectl {
protected async execute(args: string[], silent: boolean = false) { 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', () => { describe('Route strategy type', () => {
test('it has required values', () => { test('it has required values', () => {
+1 -4
View File
@@ -1,7 +1,4 @@
import { import {parseTrafficSplitMethod, TrafficSplitMethod} from './trafficSplitMethod'
parseTrafficSplitMethod,
TrafficSplitMethod
} from './trafficSplitMethod.js'
describe('Traffic split method type', () => { describe('Traffic split method type', () => {
test('it has required values', () => { 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', () => { describe('array utilities', () => {
it('creates an inline array', () => { 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 * as io from '@actions/io'
import {checkDockerPath} from './dockerUtils.js' import {checkDockerPath} from './dockerUtils'
describe('docker utilities', () => { describe('docker utilities', () => {
it('checks if docker is installed', async () => { it('checks if docker is installed', async () => {
// docker installed // docker installed
const path = 'path' const path = 'path'
vi.spyOn(io, 'which').mockImplementationOnce(async () => path) jest.spyOn(io, 'which').mockImplementationOnce(async () => path)
expect(() => checkDockerPath()).not.toThrow() expect(() => checkDockerPath()).not.toThrow()
// docker not installed // docker not installed
vi.spyOn(io, 'which').mockImplementationOnce(async () => { jest.spyOn(io, 'which').mockImplementationOnce(async () => {
throw new Error('not found') throw new Error('not found')
}) })
await expect(() => checkDockerPath()).rejects.toThrow() await expect(() => checkDockerPath()).rejects.toThrow()
+3 -3
View File
@@ -1,8 +1,8 @@
import * as io from '@actions/io' import * as io from '@actions/io'
import {DeploymentConfig} from '../types/deploymentConfig.js' import {DeploymentConfig} from '../types/deploymentConfig'
import * as core from '@actions/core' import * as core from '@actions/core'
import {DockerExec} from '../types/docker.js' import {DockerExec} from '../types/docker'
import {getNormalizedPath} from './githubUtils.js' import {getNormalizedPath} from './githubUtils'
export async function getDeploymentConfig(): Promise<DeploymentConfig> { export async function getDeploymentConfig(): Promise<DeploymentConfig> {
let helmChartPaths: string[] = let helmChartPaths: string[] =
+5 -6
View File
@@ -1,10 +1,9 @@
import {vi, type Mocked} from 'vitest' import {parseDuration} from './durationUtils'
import {parseDuration} from './durationUtils.js'
import * as core from '@actions/core' import * as core from '@actions/core'
// Mock core.debug // Mock core.debug
vi.mock('@actions/core') jest.mock('@actions/core')
const mockCore = core as Mocked<typeof core> const mockCore = core as jest.Mocked<typeof core>
// Test data constants // Test data constants
const VALID_TIMEOUTS = { const VALID_TIMEOUTS = {
@@ -47,7 +46,7 @@ const expectInvalidTimeout = (input: string, expectedError: string) => {
describe('validateTimeoutDuration', () => { describe('validateTimeoutDuration', () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks() jest.clearAllMocks()
}) })
describe('valid timeout formats', () => { describe('valid timeout formats', () => {
@@ -91,7 +90,7 @@ describe('validateTimeoutDuration', () => {
'No unit specified for timeout "5", assuming minutes' 'No unit specified for timeout "5", assuming minutes'
) )
vi.clearAllMocks() jest.clearAllMocks()
parseDuration('30s') parseDuration('30s')
expect(mockCore.debug).not.toHaveBeenCalled() expect(mockCore.debug).not.toHaveBeenCalled()
+14 -365
View File
@@ -1,18 +1,13 @@
import {vi} from 'vitest' import * as fileUtils from './fileUtils'
import * as fileUtils from './fileUtils.js'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import fs from 'node:fs' import fs from 'node:fs'
import os from 'node:os'
import * as path from 'path' import * as path from 'path'
import {K8sObject} from '../types/k8sObject.js' import {K8sObject} from '../types/k8sObject'
const sampleYamlUrl = const sampleYamlUrl =
'https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/controllers/nginx-deployment.yaml' 'https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/controllers/nginx-deployment.yaml'
describe('File utils', () => { describe('File utils', () => {
beforeAll(() => {
process.env.GITHUB_WORKSPACE ??= process.cwd()
})
test('correctly parses a yaml file from a URL', async () => { test('correctly parses a yaml file from a URL', async () => {
const tempFile = await fileUtils.writeYamlFromURLToFile(sampleYamlUrl, 0) const tempFile = await fileUtils.writeYamlFromURLToFile(sampleYamlUrl, 0)
const fileContents = fs.readFileSync(tempFile).toString() const fileContents = fs.readFileSync(tempFile).toString()
@@ -57,7 +52,7 @@ describe('File utils', () => {
expect(testSearch).toHaveLength(10) expect(testSearch).toHaveLength(10)
expectedManifests.forEach((fileName) => { expectedManifests.forEach((fileName) => {
if (fileName.startsWith('test/unit')) { if (fileName.startsWith('test/unit')) {
expect(testSearch).toContain(path.resolve(fileName)) expect(testSearch).toContain(fileName)
} else { } else {
expect(fileName.includes(fileUtils.urlFileKind)).toBe(true) expect(fileName.includes(fileUtils.urlFileKind)).toBe(true)
expect(fileName.startsWith(fileUtils.getTempDirectory())) expect(fileName.startsWith(fileUtils.getTempDirectory()))
@@ -74,7 +69,7 @@ describe('File utils', () => {
'manifest_test_dir' 'manifest_test_dir'
) )
await expect( expect(
fileUtils.getFilesFromDirectoriesAndURLs([badPath, goodPath]) fileUtils.getFilesFromDirectoriesAndURLs([badPath, goodPath])
).rejects.toThrow() ).rejects.toThrow()
}) })
@@ -109,366 +104,20 @@ describe('File utils', () => {
fileUtils.writeYamlFromURLToFile(badUrl, 0) fileUtils.writeYamlFromURLToFile(badUrl, 0)
).rejects.toBeTruthy() ).rejects.toBeTruthy()
}) })
it('rejects manifest inputs that resolve outside the workspace', async () => {
const originalWs = process.env.GITHUB_WORKSPACE
const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-'))
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'outside-'))
fs.writeFileSync(path.join(outside, 'secrets.yaml'), 'api_key: x')
process.env.GITHUB_WORKSPACE = ws
try {
await expect(
fileUtils.getFilesFromDirectoriesAndURLs([outside])
).rejects.toThrow(/outside the workspace/)
await expect(
fileUtils.getFilesFromDirectoriesAndURLs([
path.join(outside, 'secrets.yaml')
])
).rejects.toThrow(/outside the workspace/)
} finally {
if (originalWs === undefined) delete process.env.GITHUB_WORKSPACE
else process.env.GITHUB_WORKSPACE = originalWs
fs.rmSync(ws, {recursive: true, force: true})
fs.rmSync(outside, {recursive: true, force: true})
}
})
it('rejects symlinks inside a directory that escape the workspace', async () => {
const originalWs = process.env.GITHUB_WORKSPACE
const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-'))
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'outside-'))
const escapeTarget = path.join(outside, 'passwd.yaml')
fs.writeFileSync(escapeTarget, 'root:x:0:0')
const dir = path.join(ws, 'manifests')
fs.mkdirSync(dir)
fs.symlinkSync(escapeTarget, path.join(dir, 'escape.yaml'))
process.env.GITHUB_WORKSPACE = ws
try {
await expect(
fileUtils.getFilesFromDirectoriesAndURLs([dir])
).rejects.toThrow(/outside the workspace/)
} finally {
if (originalWs === undefined) delete process.env.GITHUB_WORKSPACE
else process.env.GITHUB_WORKSPACE = originalWs
fs.rmSync(ws, {recursive: true, force: true})
fs.rmSync(outside, {recursive: true, force: true})
}
})
}) })
describe('moveFileToTmpDir', () => { describe('moving files to temp', () => {
let workspace: string it('correctly moves the contents of a file to the temporary directory', () => {
let originalWorkspace: string | undefined jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
let originalTemp: string | undefined jest.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
let originalCwd: string return 'test contents'
let tmpDir: string })
const originalFilePath = path.join('path', 'in', 'repo')
beforeEach(() => { const output = fileUtils.moveFileToTmpDir(originalFilePath)
originalWorkspace = process.env.GITHUB_WORKSPACE
originalTemp = process.env.RUNNER_TEMP
originalCwd = process.cwd()
workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-'))
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rt-'))
process.env.GITHUB_WORKSPACE = workspace
process.env.RUNNER_TEMP = tmpDir
})
afterEach(() => { expect(output).toEqual(
process.chdir(originalCwd) path.join(fileUtils.getTempDirectory(), '/path/in/repo')
if (originalWorkspace === undefined) delete process.env.GITHUB_WORKSPACE
else process.env.GITHUB_WORKSPACE = originalWorkspace
if (originalTemp === undefined) delete process.env.RUNNER_TEMP
else process.env.RUNNER_TEMP = originalTemp
fs.rmSync(workspace, {recursive: true, force: true})
fs.rmSync(tmpDir, {recursive: true, force: true})
})
it('copies a workspace file to RUNNER_TEMP using a basename-only destination', () => {
const src = path.join(workspace, 'svc.yaml')
fs.writeFileSync(src, 'kind: Service')
const out = fileUtils.moveFileToTmpDir(src)
expect(fs.realpathSync(path.dirname(out))).toBe(fs.realpathSync(tmpDir))
expect(path.basename(out)).toMatch(/^svc_\d+_\d+\.yaml$/)
expect(fs.readFileSync(out).toString()).toBe('kind: Service')
})
it('rejects relative traversal that escapes the workspace', () => {
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'outside-'))
fs.writeFileSync(path.join(outside, 'secrets.yaml'), 'api_key: x')
process.chdir(workspace)
const rel = path.relative(workspace, path.join(outside, 'secrets.yaml'))
expect(() => fileUtils.moveFileToTmpDir(rel)).toThrow(
/outside the workspace/
) )
fs.rmSync(outside, {recursive: true, force: true})
})
it('does not collide when two inputs share a basename', () => {
const a = path.join(workspace, 'a')
const b = path.join(workspace, 'b')
fs.mkdirSync(a)
fs.mkdirSync(b)
fs.writeFileSync(path.join(a, 'svc.yaml'), 'A')
fs.writeFileSync(path.join(b, 'svc.yaml'), 'B')
const outA = fileUtils.moveFileToTmpDir(path.join(a, 'svc.yaml'))
const outB = fileUtils.moveFileToTmpDir(path.join(b, 'svc.yaml'))
expect(outA).not.toBe(outB)
expect(fs.readFileSync(outA).toString()).toBe('A')
expect(fs.readFileSync(outB).toString()).toBe('B')
})
})
describe('assertPathWithinWorkspace', () => {
let workspace: string
let outside: string
let originalWorkspace: string | undefined
let originalCwd: string
beforeEach(() => {
originalWorkspace = process.env.GITHUB_WORKSPACE
originalCwd = process.cwd()
workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-'))
outside = fs.mkdtempSync(path.join(os.tmpdir(), 'outside-'))
process.env.GITHUB_WORKSPACE = workspace
})
afterEach(() => {
process.chdir(originalCwd)
if (originalWorkspace === undefined) {
delete process.env.GITHUB_WORKSPACE
} else {
process.env.GITHUB_WORKSPACE = originalWorkspace
}
fs.rmSync(workspace, {recursive: true, force: true})
fs.rmSync(outside, {recursive: true, force: true})
})
it('returns the resolved path for files inside the workspace', () => {
const inside = path.join(workspace, 'a.yaml')
fs.writeFileSync(inside, 'kind: X')
const result = fileUtils.assertPathWithinWorkspace(inside)
expect(result).toBe(fs.realpathSync(inside))
})
it('accepts workspace files whose basename starts with ..', () => {
const inside = path.join(workspace, '..bar.yaml')
fs.writeFileSync(inside, 'kind: X')
expect(fileUtils.assertPathWithinWorkspace(inside)).toBe(
fs.realpathSync(inside)
)
})
it('throws for relative traversal paths that escape the workspace', () => {
const target = path.join(outside, 'secrets.yaml')
fs.writeFileSync(target, 'api_key: secret')
const rel = path.relative(workspace, target)
process.chdir(workspace)
expect(() => fileUtils.assertPathWithinWorkspace(rel)).toThrow(
/outside the workspace/
)
})
it('resolves relative paths against GITHUB_WORKSPACE even when CWD differs', () => {
const inside = path.join(workspace, 'manifest.yaml')
fs.writeFileSync(inside, 'kind: X')
// Deliberately chdir somewhere unrelated so a process.cwd()-based
// resolver would either reject or resolve to the wrong place.
process.chdir(os.tmpdir())
const result = fileUtils.assertPathWithinWorkspace('manifest.yaml')
expect(result).toBe(fs.realpathSync(inside))
})
it('throws for absolute paths outside the workspace', () => {
const target = path.join(outside, 'secrets.yaml')
fs.writeFileSync(target, 'api_key: secret')
expect(() => fileUtils.assertPathWithinWorkspace(target)).toThrow(
/outside the workspace/
)
})
it('throws when a symlink inside the workspace points outside', () => {
const target = path.join(outside, 'secrets.yaml')
fs.writeFileSync(target, 'api_key: secret')
const link = path.join(workspace, 'evil.yaml')
fs.symlinkSync(target, link)
expect(() => fileUtils.assertPathWithinWorkspace(link)).toThrow(
/outside the workspace/
)
})
it('throws a clear error for missing files', () => {
const missing = path.join(workspace, 'nope.yaml')
expect(() => fileUtils.assertPathWithinWorkspace(missing)).toThrow(
/does not exist or is not readable/
)
})
it('skips containment when GITHUB_WORKSPACE is unset', () => {
delete process.env.GITHUB_WORKSPACE
const target = path.join(outside, 'whatever.yaml')
fs.writeFileSync(target, 'kind: X')
expect(fileUtils.assertPathWithinWorkspace(target)).toBe(target)
})
})
import {EventEmitter} from 'node:events'
import {PassThrough} from 'node:stream'
import * as https from 'node:https'
const httpsState = vi.hoisted(() => ({impl: null as any}))
vi.mock('https', async (importOriginal) => {
const actual = await importOriginal<typeof import('https')>()
const get = (...args: any[]) =>
httpsState.impl ? httpsState.impl(...args) : (actual.get as any)(...args)
return {
...actual,
default: {...actual, get},
get
}
})
describe('writeYamlFromURLToFile error handling', () => {
let tempDir: string
let originalRunnerTemp: string | undefined
beforeEach(() => {
originalRunnerTemp = process.env.RUNNER_TEMP
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'url-fetch-'))
process.env.RUNNER_TEMP = tempDir
})
afterEach(() => {
httpsState.impl = null
vi.restoreAllMocks()
if (originalRunnerTemp === undefined) {
delete process.env.RUNNER_TEMP
} else {
process.env.RUNNER_TEMP = originalRunnerTemp
}
fs.rmSync(tempDir, {recursive: true, force: true})
})
// Wait one tick so cleanupAndReject's async fs.rm callback can fire
// before the test inspects the temp directory.
const waitForCleanup = () =>
new Promise<void>((r) => setImmediate(() => setImmediate(r)))
function mockHttpsGet(
makeResponse: () => {
response: EventEmitter & {
statusCode?: number
statusMessage?: string
pipe: PassThrough['pipe']
resume: () => void
}
requestEmitter: EventEmitter
}
) {
httpsState.impl = ((url: string, cb?: any) => {
const {response, requestEmitter} = makeResponse()
if (cb) setImmediate(() => cb(response))
return requestEmitter as any
}) as any
}
it('rejects on HTTP 500 without writing a file', async () => {
const requestEmitter = new EventEmitter()
const response = Object.assign(new PassThrough(), {
statusCode: 500,
statusMessage: 'Server Error',
resume() {
/* drain */
}
})
mockHttpsGet(() => ({response: response as any, requestEmitter}))
await expect(
fileUtils.writeYamlFromURLToFile('https://example.com/x.yaml', 99)
).rejects.toThrow(/Server Error/)
})
it('rejects when the response stream errors mid-download', async () => {
const requestEmitter = new EventEmitter()
const response = Object.assign(new PassThrough(), {
statusCode: 200,
statusMessage: 'OK',
resume() {}
})
mockHttpsGet(() => ({response: response as any, requestEmitter}))
const p = fileUtils.writeYamlFromURLToFile(
'https://example.com/y.yaml',
100
)
setImmediate(() => response.emit('error', new Error('socket reset')))
await expect(p).rejects.toThrow(/socket reset/)
})
it('rejects on request-level errors', async () => {
const requestEmitter = new EventEmitter()
const response = Object.assign(new PassThrough(), {
statusCode: 200,
resume() {}
})
mockHttpsGet(() => ({response: response as any, requestEmitter}))
const p = fileUtils.writeYamlFromURLToFile(
'https://example.com/z.yaml',
101
)
setImmediate(() => requestEmitter.emit('error', new Error('DNS failure')))
await expect(p).rejects.toThrow(/DNS failure/)
})
it('removes temp file when verification fails', async () => {
const requestEmitter = new EventEmitter()
const response = Object.assign(new PassThrough(), {
statusCode: 200,
statusMessage: 'OK'
})
mockHttpsGet(() => ({response: response as any, requestEmitter}))
const before = new Set(fs.readdirSync(tempDir))
const p = fileUtils.writeYamlFromURLToFile(
'https://example.com/bad.yaml',
200
)
// Stream a YAML document missing required k8s fields so verifyYaml fails.
setImmediate(() => {
response.end('not: a-real-manifest\n')
})
await expect(p).rejects.toThrow(/missing fields|failed to parse/)
await waitForCleanup()
const after = fs.readdirSync(tempDir).filter((f) => !before.has(f))
expect(after).toEqual([])
})
it('removes temp file on mid-stream response error', async () => {
const requestEmitter = new EventEmitter()
const response = Object.assign(new PassThrough(), {
statusCode: 200,
statusMessage: 'OK',
resume() {}
})
mockHttpsGet(() => ({response: response as any, requestEmitter}))
const before = new Set(fs.readdirSync(tempDir))
const p = fileUtils.writeYamlFromURLToFile(
'https://example.com/midstream.yaml',
201
)
setImmediate(() => {
response.write('kind: Foo\n')
response.emit('error', new Error('socket reset'))
})
await expect(p).rejects.toThrow(/socket reset/)
await waitForCleanup()
const after = fs.readdirSync(tempDir).filter((f) => !before.has(f))
expect(after).toEqual([])
}) })
}) })
+32 -91
View File
@@ -4,63 +4,17 @@ import * as path from 'path'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as os from 'os' import * as os from 'os'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import {Errorable, succeeded, failed, Failed} from '../types/errorable.js' import {Errorable, succeeded, failed, Failed} from '../types/errorable'
import {getCurrentTime} from './timeUtils.js' import {getCurrentTime} from './timeUtils'
import {isHttpUrl} from './githubUtils.js' import {isHttpUrl} from './githubUtils'
import {K8sObject} from '../types/k8sObject.js' import {K8sObject} from '../types/k8sObject'
export const urlFileKind = 'urlfile' export const urlFileKind = 'urlfile'
let moveCounter = 0
export function getTempDirectory(): string { export function getTempDirectory(): string {
return process.env['RUNNER_TEMP'] || os.tmpdir() return process.env['RUNNER_TEMP'] || os.tmpdir()
} }
// Exported for tests. Validates that `inputPath` resolves (after symlink
// resolution) to a location inside GITHUB_WORKSPACE. When GITHUB_WORKSPACE
// is not set (e.g. local dev / unit tests), the check is skipped — callers
// that write to RUNNER_TEMP still get protection from basename-only
// destinations.
export function assertPathWithinWorkspace(inputPath: string): string {
const workspace = process.env.GITHUB_WORKSPACE
if (!workspace) {
core.warning(
'GITHUB_WORKSPACE is not set; skipping manifest path containment check'
)
return inputPath
}
const resolvedWorkspace = fs.realpathSync(path.resolve(workspace))
// Resolve relative inputs against the workspace (not process.cwd()), so
// a relative `manifests:` input is interpreted consistently regardless of
// whether a prior step changed the working directory. Absolute paths are
// passed through unchanged and still validated below.
const absoluteInput = path.isAbsolute(inputPath)
? inputPath
: path.resolve(resolvedWorkspace, inputPath)
let resolvedInput: string
try {
resolvedInput = fs.realpathSync(absoluteInput)
} catch (e) {
throw new Error(
`manifest path ${inputPath} does not exist or is not readable: ${e}`
)
}
const rel = path.relative(resolvedWorkspace, resolvedInput)
if (
rel === '' ||
(rel !== '..' &&
!rel.startsWith('..' + path.sep) &&
!path.isAbsolute(rel))
) {
return resolvedInput
}
throw new Error(
`manifest path ${inputPath} resolves to ${resolvedInput}, ` +
`which is outside the workspace ${resolvedWorkspace}`
)
}
export function writeObjectsToFile(inputObjects: any[]): string[] { export function writeObjectsToFile(inputObjects: any[]): string[] {
const newFilePaths = [] const newFilePaths = []
@@ -110,20 +64,22 @@ export function writeManifestToFile(
} }
export function moveFileToTmpDir(originalFilepath: string) { export function moveFileToTmpDir(originalFilepath: string) {
const safeSource = assertPathWithinWorkspace(originalFilepath)
const tempDirectory = getTempDirectory() const tempDirectory = getTempDirectory()
const ext = path.extname(safeSource) const newPath = path.join(tempDirectory, originalFilepath)
const base = path.basename(safeSource, ext)
const uniqueName = `${base}_${getCurrentTime()}_${moveCounter++}${ext}`
const newPath = path.join(tempDirectory, uniqueName)
core.debug(`reading original contents from path: ${originalFilepath}`) core.debug(`reading original contents from path: ${originalFilepath}`)
const contents = fs.readFileSync(safeSource) const contents = fs.readFileSync(originalFilepath).toString()
const dirName = path.dirname(newPath)
if (!fs.existsSync(dirName)) {
core.debug(`path ${dirName} doesn't exist yet, making new dir...`)
fs.mkdirSync(dirName, {recursive: true})
}
core.debug(`writing contents to new path ${newPath}`) core.debug(`writing contents to new path ${newPath}`)
fs.writeFileSync(newPath, contents) fs.writeFileSync(path.join(newPath), contents)
core.debug(`moved contents from ${originalFilepath} to ${newPath}`) core.debug(`moved contents from ${originalFilepath} to ${newPath}`)
return newPath return newPath
} }
@@ -153,20 +109,15 @@ export async function getFilesFromDirectoriesAndURLs(
`encountered error trying to pull YAML from URL ${fileName}: ${e}` `encountered error trying to pull YAML from URL ${fileName}: ${e}`
) )
} }
continue } else if (fs.lstatSync(fileName).isDirectory()) {
} recurisveManifestGetter(fileName).forEach((file) => {
const safePath = assertPathWithinWorkspace(fileName)
if (fs.lstatSync(safePath).isDirectory()) {
recurisveManifestGetter(safePath).forEach((file) => {
fullPathSet.add(file) fullPathSet.add(file)
}) })
} else if ( } else if (
getFileExtension(safePath) === 'yml' || getFileExtension(fileName) === 'yml' ||
getFileExtension(safePath) === 'yaml' getFileExtension(fileName) === 'yaml'
) { ) {
fullPathSet.add(safePath) fullPathSet.add(fileName)
} else { } else {
core.debug( core.debug(
`Detected non-manifest file, ${fileName}, continuing... ` `Detected non-manifest file, ${fileName}, continuing... `
@@ -189,33 +140,24 @@ export async function writeYamlFromURLToFile(
): Promise<string> { ): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
https https
.get(url, (response) => { .get(url, async (response) => {
const code = response.statusCode ?? 0 const code = response.statusCode ?? 0
if (code >= 400) { if (code >= 400) {
response.resume()
reject( reject(
new Error( Error(
`received response status ${response.statusMessage} from url ${url}` `received response status ${response.statusMessage} from url ${url}`
) )
) )
return
} }
const targetPath = getNewTempManifestFileName( const targetPath = getNewTempManifestFileName(
urlFileKind, urlFileKind,
fileNumber.toString() fileNumber.toString()
) )
// Once the write stream is created the file exists on disk; // save the file to disk
// route all post-stream rejections through this helper so we const fileWriter = fs
// don't leave truncated YAML in RUNNER_TEMP for later tooling .createWriteStream(targetPath)
// to pick up. Do NOT unlink on the success path. .on('finish', () => {
const cleanupAndReject = (err: unknown) => {
fs.rm(targetPath, {force: true}, () => reject(err))
}
const fileWriter = fs.createWriteStream(targetPath)
fileWriter.on('error', cleanupAndReject)
fileWriter.on('finish', () => {
try {
const verification = verifyYaml(targetPath, url) const verification = verifyYaml(targetPath, url)
if (succeeded(verification)) { if (succeeded(verification)) {
core.debug( core.debug(
@@ -225,16 +167,15 @@ export async function writeYamlFromURLToFile(
) )
resolve(targetPath) resolve(targetPath)
} else { } else {
cleanupAndReject(new Error(verification.error)) reject(verification.error)
} }
} catch (e) { })
cleanupAndReject(e)
}
})
response.on('error', cleanupAndReject)
response.pipe(fileWriter) response.pipe(fileWriter)
}) })
.on('error', reject) .on('error', (error) => {
reject(error)
})
}) })
} }
@@ -258,7 +199,7 @@ function verifyYaml(filepath: string, url: string): Errorable<K8sObject[]> {
} }
for (const obj of inputObjects) { for (const obj of inputObjects) {
if (obj == null || !obj.kind || !obj.apiVersion || !obj.metadata) { if (!obj.kind || !obj.apiVersion || !obj.metadata) {
return { return {
succeeded: false, succeeded: false,
error: `failed to parse manifest from ${url}: missing fields` error: `failed to parse manifest from ${url}: missing fields`
@@ -280,7 +221,7 @@ function recurisveManifestGetter(dirName: string): string[] {
getFileExtension(fileName) === 'yml' || getFileExtension(fileName) === 'yml' ||
getFileExtension(fileName) === 'yaml' getFileExtension(fileName) === 'yaml'
) { ) {
toRet.push(assertPathWithinWorkspace(fnwd)) toRet.push(path.join(dirName, fileName))
} else { } else {
core.debug(`Detected non-manifest file, ${fileName}, continuing... `) core.debug(`Detected non-manifest file, ${fileName}, continuing... `)
} }
+1 -1
View File
@@ -2,7 +2,7 @@ import {
getNormalizedPath, getNormalizedPath,
isHttpUrl, isHttpUrl,
normalizeWorkflowStrLabel normalizeWorkflowStrLabel
} from './githubUtils.js' } from './githubUtils'
describe('Github utils', () => { describe('Github utils', () => {
it('normalizes workflow string labels', () => { 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' import * as core from '@actions/core'
export async function getWorkflowFilePath( 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 * as core from '@actions/core'
import {ExecOutput} from '@actions/exec' import {ExecOutput} from '@actions/exec'
import {checkForErrors} from './kubectlUtils.js' import {checkForErrors} from './kubectlUtils'
describe('Kubectl utils', () => { describe('Kubectl utils', () => {
it('checks for errors', () => { it('checks for errors', () => {
@@ -42,8 +39,7 @@ describe('Kubectl utils', () => {
).toThrow() ).toThrow()
// with warn behavior // with warn behavior
const warnSpy = vi.spyOn(core, 'warning').mockImplementation(() => {}) jest.spyOn(core, 'warning').mockImplementation(() => {})
warnSpy.mockClear()
let warningCalls = 0 let warningCalls = 0
expect(() => checkForErrors([success], true)).not.toThrow() expect(() => checkForErrors([success], true)).not.toThrow()
expect(core.warning).toHaveBeenCalledTimes(warningCalls) expect(core.warning).toHaveBeenCalledTimes(warningCalls)
+1 -1
View File
@@ -1,6 +1,6 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {ExecOutput} from '@actions/exec' import {ExecOutput} from '@actions/exec'
import {Kubectl} from '../types/kubectl.js' import {Kubectl} from '../types/kubectl'
const NAMESPACE = 'namespace' 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) { export function getImagePullSecrets(inputObject: any) {
const kind = inputObject?.kind?.toLowerCase() const kind = inputObject?.kind?.toLowerCase()
+1 -1
View File
@@ -3,7 +3,7 @@ import {
isServiceEntity, isServiceEntity,
KubernetesWorkload, KubernetesWorkload,
NullInputObjectError NullInputObjectError
} from '../types/kubernetesTypes.js' } from '../types/kubernetesTypes'
export function updateSpecLabels( export function updateSpecLabels(
inputObject: any, inputObject: any,
+45 -106
View File
@@ -1,29 +1,10 @@
import {vi} from 'vitest' import * as manifestStabilityUtils from './manifestStabilityUtils'
import type {MockInstance} from 'vitest' import {Kubectl} from '../types/kubectl'
vi.mock('@actions/core', async (importOriginal) => { import {ResourceTypeFleet, ResourceTypeManagedCluster} from '../actions/deploy'
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 {ExecOutput} from '@actions/exec' import {ExecOutput} from '@actions/exec'
import {exitCode, stdout} from 'process' import {exitCode, stdout} from 'process'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as timeUtils from './timeUtils.js' import * as timeUtils from './timeUtils'
describe('manifestStabilityUtils', () => { describe('manifestStabilityUtils', () => {
const kc = new Kubectl('') const kc = new Kubectl('')
@@ -36,8 +17,8 @@ describe('manifestStabilityUtils', () => {
] ]
it('should return immediately if the resource type is fleet', async () => { it('should return immediately if the resource type is fleet', async () => {
const spy = vi.spyOn(manifestStabilityUtils, 'checkManifestStability') const spy = jest.spyOn(manifestStabilityUtils, 'checkManifestStability')
const checkRolloutStatusSpy = vi.spyOn(kc, 'checkRolloutStatus') const checkRolloutStatusSpy = jest.spyOn(kc, 'checkRolloutStatus')
await manifestStabilityUtils.checkManifestStability( await manifestStabilityUtils.checkManifestStability(
kc, kc,
resources, resources,
@@ -49,8 +30,8 @@ describe('manifestStabilityUtils', () => {
}) })
it('should run fully if the resource type is managedCluster', async () => { it('should run fully if the resource type is managedCluster', async () => {
const spy = vi.spyOn(manifestStabilityUtils, 'checkManifestStability') const spy = jest.spyOn(manifestStabilityUtils, 'checkManifestStability')
const checkRolloutStatusSpy = vi const checkRolloutStatusSpy = jest
.spyOn(kc, 'checkRolloutStatus') .spyOn(kc, 'checkRolloutStatus')
.mockImplementation(() => { .mockImplementation(() => {
return new Promise<ExecOutput>((resolve, reject) => { return new Promise<ExecOutput>((resolve, reject) => {
@@ -73,7 +54,7 @@ describe('manifestStabilityUtils', () => {
it('should pass timeout to checkRolloutStatus when provided', async () => { it('should pass timeout to checkRolloutStatus when provided', async () => {
const timeout = '300s' const timeout = '300s'
const checkRolloutStatusSpy = vi const checkRolloutStatusSpy = jest
.spyOn(kc, 'checkRolloutStatus') .spyOn(kc, 'checkRolloutStatus')
.mockImplementation(() => { .mockImplementation(() => {
return new Promise<ExecOutput>((resolve, reject) => { return new Promise<ExecOutput>((resolve, reject) => {
@@ -101,7 +82,7 @@ describe('manifestStabilityUtils', () => {
}) })
it('should call checkRolloutStatus without timeout when not provided', async () => { it('should call checkRolloutStatus without timeout when not provided', async () => {
const checkRolloutStatusSpy = vi const checkRolloutStatusSpy = jest
.spyOn(kc, 'checkRolloutStatus') .spyOn(kc, 'checkRolloutStatus')
.mockImplementation(() => { .mockImplementation(() => {
return new Promise<ExecOutput>((resolve, reject) => { return new Promise<ExecOutput>((resolve, reject) => {
@@ -130,76 +111,34 @@ describe('manifestStabilityUtils', () => {
describe('checkManifestStability failure and resource-specific scenarios', () => { describe('checkManifestStability failure and resource-specific scenarios', () => {
let kc: Kubectl let kc: Kubectl
let coreErrorSpy: MockInstance let coreErrorSpy: jest.SpyInstance
let coreInfoSpy: MockInstance let coreInfoSpy: jest.SpyInstance
let coreWarningSpy: MockInstance let coreWarningSpy: jest.SpyInstance
beforeEach(() => { beforeEach(() => {
kc = new Kubectl('', 'default') kc = new Kubectl('')
coreErrorSpy = vi.spyOn(core, 'error').mockImplementation(() => {}) coreErrorSpy = jest.spyOn(core, 'error').mockImplementation()
coreInfoSpy = vi.spyOn(core, 'info').mockImplementation(() => {}) coreInfoSpy = jest.spyOn(core, 'info').mockImplementation()
coreWarningSpy = vi.spyOn(core, 'warning').mockImplementation(() => {}) coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation()
}) })
afterEach(() => { afterEach(() => {
vi.restoreAllMocks() jest.restoreAllMocks()
}) })
it('should call describe and collect errors when a rollout fails', async () => { it('should call describe and collect errors when a rollout fails', async () => {
const resources = [ 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 rolloutError = new Error('Progress deadline exceeded')
const describeOutput = const describeOutput =
'Events:\n Type\tReason\tMessage\n Normal\tScalingReplicaSet\tScaled up replica set failing-app-123 to 1' '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 // Arrange: Mock rollout to fail and describe to succeed
const checkRolloutStatusSpy = vi const checkRolloutStatusSpy = jest
.spyOn(kc, 'checkRolloutStatus') .spyOn(kc, 'checkRolloutStatus')
.mockRejectedValue(rolloutError) .mockRejectedValue(rolloutError)
const describeSpy = vi.spyOn(kc, 'describe').mockResolvedValue({ const describeSpy = jest.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({
stdout: describeOutput, stdout: describeOutput,
stderr: '', stderr: '',
exitCode: 0 exitCode: 0
@@ -224,7 +163,7 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
'deployment', 'deployment',
'failing-app', 'failing-app',
false, false,
undefined 'default'
) )
expect(coreInfoSpy).toHaveBeenCalledWith( expect(coreInfoSpy).toHaveBeenCalledWith(
`Describe output for deployment/failing-app:\n${describeOutput}` `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'}] const resources = [{type: 'Pod', name: 'test-pod', namespace: 'default'}]
// Arrange: Spy on checkPodStatus and checkRolloutStatus // Arrange: Spy on checkPodStatus and checkRolloutStatus
const checkPodStatusSpy = vi const checkPodStatusSpy = jest
.spyOn(manifestStabilityUtils, 'checkPodStatus') .spyOn(manifestStabilityUtils, 'checkPodStatus')
.mockResolvedValue() // Assume pod becomes ready .mockResolvedValue() // Assume pod becomes ready
const checkRolloutStatusSpy = vi.spyOn(kc, 'checkRolloutStatus') const checkRolloutStatusSpy = jest.spyOn(kc, 'checkRolloutStatus')
// Act // Act
await manifestStabilityUtils.checkManifestStability( await manifestStabilityUtils.checkManifestStability(
@@ -259,10 +198,10 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
const podError = new Error('Pod rollout failed') const podError = new Error('Pod rollout failed')
// Arrange: Mock checkPodStatus to fail // Arrange: Mock checkPodStatus to fail
const checkPodStatusSpy = vi const checkPodStatusSpy = jest
.spyOn(manifestStabilityUtils, 'checkPodStatus') .spyOn(manifestStabilityUtils, 'checkPodStatus')
.mockRejectedValue(podError) .mockRejectedValue(podError)
const describeSpy = vi.spyOn(kc, 'describe').mockResolvedValue({ const describeSpy = jest.spyOn(kc, 'describe').mockResolvedValue({
stdout: 'describe output', stdout: 'describe output',
stderr: '', stderr: '',
exitCode: 0 exitCode: 0
@@ -290,7 +229,7 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
it('should wait for external IP for a LoadBalancer service', async () => { it('should wait for external IP for a LoadBalancer service', async () => {
//Spying on sleep to avoid actual delays in tests //Spying on sleep to avoid actual delays in tests
vi.spyOn(timeUtils, 'sleep').mockResolvedValue(undefined) jest.spyOn(timeUtils, 'sleep').mockResolvedValue(undefined)
const resources = [ const resources = [
{type: 'service', name: 'test-svc', namespace: 'default'} {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 // Arrange: Mock getResource to simulate the IP being assigned on the second poll
const getResourceSpy = vi const getResourceSpy = jest
.spyOn(kc, 'getResource') .spyOn(kc, 'getResource')
// First call: Initial service check // First call: Initial service check
.mockResolvedValueOnce({ .mockResolvedValueOnce({
@@ -347,10 +286,10 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
// Arrange: Mock getService to fail, and describe to succeed // Arrange: Mock getService to fail, and describe to succeed
// Note: We mock getResource because getService is a private helper // Note: We mock getResource because getService is a private helper
const getResourceSpy = vi const getResourceSpy = jest
.spyOn(kc, 'getResource') .spyOn(kc, 'getResource')
.mockRejectedValue(getServiceError) .mockRejectedValue(getServiceError)
const describeSpy = vi.spyOn(kc, 'describe').mockResolvedValue({ const describeSpy = jest.spyOn(kc, 'describe').mockResolvedValue({
stdout: 'describe output', stdout: 'describe output',
stderr: '', stderr: '',
exitCode: 0 exitCode: 0
@@ -388,7 +327,7 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
} }
// Arrange // Arrange
const getResourceSpy = vi.spyOn(kc, 'getResource').mockResolvedValue({ const getResourceSpy = jest.spyOn(kc, 'getResource').mockResolvedValue({
stdout: JSON.stringify(clusterIpService), stdout: JSON.stringify(clusterIpService),
stderr: '', stderr: '',
exitCode: 0 exitCode: 0
@@ -411,19 +350,19 @@ describe('checkManifestStability failure and resource-specific scenarios', () =>
describe('checkManifestStability additional scenarios', () => { describe('checkManifestStability additional scenarios', () => {
let kc: Kubectl let kc: Kubectl
let coreErrorSpy: MockInstance let coreErrorSpy: jest.SpyInstance
let coreInfoSpy: MockInstance let coreInfoSpy: jest.SpyInstance
let coreWarningSpy: MockInstance let coreWarningSpy: jest.SpyInstance
beforeEach(() => { beforeEach(() => {
kc = new Kubectl('') kc = new Kubectl('')
coreErrorSpy = vi.spyOn(core, 'error').mockImplementation(() => {}) coreErrorSpy = jest.spyOn(core, 'error').mockImplementation()
coreInfoSpy = vi.spyOn(core, 'info').mockImplementation(() => {}) coreInfoSpy = jest.spyOn(core, 'info').mockImplementation()
coreWarningSpy = vi.spyOn(core, 'warning').mockImplementation(() => {}) coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation()
}) })
afterEach(() => { afterEach(() => {
vi.restoreAllMocks() jest.restoreAllMocks()
}) })
it('should aggregate errors from deployment and pod failures', async () => { 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') const podError = new Error('Pod not ready in time')
// Arrange: Mock failures // Arrange: Mock failures
const checkRolloutStatusSpy = vi const checkRolloutStatusSpy = jest
.spyOn(kc, 'checkRolloutStatus') .spyOn(kc, 'checkRolloutStatus')
.mockRejectedValue(deploymentError) .mockRejectedValue(deploymentError)
// For pod: simulate a pod check failure // For pod: simulate a pod check failure
const checkPodStatusSpy = vi const checkPodStatusSpy = jest
.spyOn(manifestStabilityUtils, 'checkPodStatus') .spyOn(manifestStabilityUtils, 'checkPodStatus')
.mockRejectedValue(podError) .mockRejectedValue(podError)
// For both, simulate a successful describe call to provide additional details // 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', stdout: 'describe aggregated output',
stderr: '', stderr: '',
exitCode: 0 exitCode: 0
@@ -482,25 +421,25 @@ describe('checkManifestStability additional scenarios', () => {
// Arrange: // Arrange:
// Deployment rollout succeeds // Deployment rollout succeeds
vi.spyOn(kc, 'checkRolloutStatus').mockResolvedValue({ jest.spyOn(kc, 'checkRolloutStatus').mockResolvedValue({
exitCode: 0, exitCode: 0,
stderr: '', stderr: '',
stdout: '' stdout: ''
}) })
// Pod becomes ready // Pod becomes ready
vi.spyOn(manifestStabilityUtils, 'checkPodStatus').mockResolvedValue() jest.spyOn(manifestStabilityUtils, 'checkPodStatus').mockResolvedValue()
// Simulate a LoadBalancer service that already has an external IP // Simulate a LoadBalancer service that already has an external IP
const stableService = { const stableService = {
spec: {type: 'LoadBalancer'}, spec: {type: 'LoadBalancer'},
status: {loadBalancer: {ingress: [{ip: '1.2.3.4'}]}} status: {loadBalancer: {ingress: [{ip: '1.2.3.4'}]}}
} }
vi.spyOn(kc, 'getResource').mockResolvedValue({ jest.spyOn(kc, 'getResource').mockResolvedValue({
stdout: JSON.stringify(stableService), stdout: JSON.stringify(stableService),
stderr: '', stderr: '',
exitCode: 0 exitCode: 0
}) })
// Provide a describe result to avoid warnings // Provide a describe result to avoid warnings
vi.spyOn(kc, 'describe').mockResolvedValue({ jest.spyOn(kc, 'describe').mockResolvedValue({
stdout: 'describe output stable', stdout: 'describe output stable',
stderr: '', stderr: '',
exitCode: 0 exitCode: 0
+8 -8
View File
@@ -1,10 +1,10 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as KubernetesConstants from '../types/kubernetesTypes.js' import * as KubernetesConstants from '../types/kubernetesTypes'
import {Kubectl, Resource} from '../types/kubectl.js' import {Kubectl, Resource} from '../types/kubectl'
import {checkForErrors} from './kubectlUtils.js' import {checkForErrors} from './kubectlUtils'
import {sleep} from './timeUtils.js' import {sleep} from './timeUtils'
import {ResourceTypeFleet} from '../actions/deploy.js' import {ResourceTypeFleet} from '../actions/deploy'
import {ClusterType} from '../inputUtils.js' import {ClusterType} from '../inputUtils'
const IS_SILENT = false const IS_SILENT = false
const POD = 'pod' const POD = 'pod'
@@ -46,7 +46,7 @@ export async function checkManifestStability(
) )
checkForErrors([result]) checkForErrors([result])
} catch (ex) { } 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) core.error(errorMessage)
rolloutErrors.push(errorMessage) rolloutErrors.push(errorMessage)
@@ -106,7 +106,7 @@ export async function checkManifestStability(
} }
} }
} catch (ex) { } 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) core.warning(errorMessage)
try { try {
+5 -8
View File
@@ -1,17 +1,14 @@
import {vi} from 'vitest' import * as fileUtils from './fileUtils'
vi.mock('fs') import * as manifestUpdateUtils from './manifestUpdateUtils'
import * as fileUtils from './fileUtils.js'
import * as manifestUpdateUtils from './manifestUpdateUtils.js'
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs' import * as fs from 'fs'
describe('manifestUpdateUtils', () => { describe('manifestUpdateUtils', () => {
vi.spyOn(fileUtils, 'moveFileToTmpDir').mockImplementation((filename) => { jest.spyOn(fileUtils, 'moveFileToTmpDir').mockImplementation((filename) => {
return path.join('/tmp', filename) return path.join('/tmp', filename)
}) })
vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {}) jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
vi.spyOn(fs, 'readFileSync').mockImplementation((filename) => { jest.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
return 'test contents' return 'test contents'
}) })
+7 -7
View File
@@ -2,25 +2,25 @@ import * as core from '@actions/core'
import * as fs from 'fs' import * as fs from 'fs'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import * as path from 'path' import * as path from 'path'
import * as fileHelper from './fileUtils.js' import * as fileHelper from './fileUtils'
import {moveFileToTmpDir} from './fileUtils.js' import {moveFileToTmpDir} from './fileUtils'
import { import {
InputObjectKindNotDefinedError, InputObjectKindNotDefinedError,
InputObjectMetadataNotDefinedError, InputObjectMetadataNotDefinedError,
isWorkloadEntity, isWorkloadEntity,
KubernetesWorkload, KubernetesWorkload,
NullInputObjectError NullInputObjectError
} from '../types/kubernetesTypes.js' } from '../types/kubernetesTypes'
import { import {
getSpecSelectorLabels, getSpecSelectorLabels,
setSpecSelectorLabels setSpecSelectorLabels
} from './manifestSpecLabelUtils.js' } from './manifestSpecLabelUtils'
import { import {
getImagePullSecrets, getImagePullSecrets,
setImagePullSecrets setImagePullSecrets
} from './manifestPullSecretUtils.js' } from './manifestPullSecretUtils'
import {Resource} from '../types/kubectl.js' import {Resource} from '../types/kubectl'
import {K8sObject} from '../types/k8sObject.js' import {K8sObject} from '../types/k8sObject'
export function updateManifestFiles(manifestFilePaths: string[]) { export function updateManifestFiles(manifestFilePaths: string[]) {
if (manifestFilePaths?.length === 0) { if (manifestFilePaths?.length === 0) {
+5 -4
View File
@@ -1,12 +1,11 @@
import { import {
getImagePullSecrets, getImagePullSecrets,
setImagePullSecrets setImagePullSecrets
} from './manifestPullSecretUtils.js' } from './manifestPullSecretUtils'
import {updateSpecLabels} from './manifestSpecLabelUtils.js' import {updateSpecLabels} from './manifestSpecLabelUtils'
import {getReplicaCount} from './manifestUpdateUtils.js' import {getReplicaCount} from './manifestUpdateUtils'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import * as fs from 'fs' import * as fs from 'fs'
import {isWorkloadEntity, isDeploymentEntity} from '../types/kubernetesTypes.js'
describe('ScaledJob Support', () => { describe('ScaledJob Support', () => {
let scaledJobObject: any let scaledJobObject: any
@@ -58,11 +57,13 @@ describe('ScaledJob Support', () => {
describe('Workload Classification', () => { describe('Workload Classification', () => {
it('should classify ScaledJob as workload entity', () => { it('should classify ScaledJob as workload entity', () => {
const {isWorkloadEntity} = require('../types/kubernetesTypes')
expect(isWorkloadEntity('ScaledJob')).toBe(true) expect(isWorkloadEntity('ScaledJob')).toBe(true)
expect(isWorkloadEntity('scaledjob')).toBe(true) expect(isWorkloadEntity('scaledjob')).toBe(true)
}) })
it('should not classify ScaledJob as deployment entity', () => { it('should not classify ScaledJob as deployment entity', () => {
const {isDeploymentEntity} = require('../types/kubernetesTypes')
expect(isDeploymentEntity('scaledjob')).toBe(false) expect(isDeploymentEntity('scaledjob')).toBe(false)
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' const trafficSplitAPIVersionPrefix = 'split.smi-spec.io'
@@ -2,7 +2,7 @@ import {
cleanLabel, cleanLabel,
removeInvalidLabelCharacters, removeInvalidLabelCharacters,
VALID_LABEL_REGEX VALID_LABEL_REGEX
} from '../utilities/workflowAnnotationUtils.js' } from '../utilities/workflowAnnotationUtils'
describe('WorkflowAnnotationUtils', () => { describe('WorkflowAnnotationUtils', () => {
describe('cleanLabel', () => { 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' const ANNOTATION_PREFIX = 'actions.github.com'
+4 -11
View File
@@ -1,15 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES6",
"module": "NodeNext", "module": "commonjs",
"moduleResolution": "NodeNext", "esModuleInterop": true
"types": ["vitest/globals"],
"esModuleInterop": true,
"strict": false,
"skipLibCheck": true,
"resolveJsonModule": true,
"declaration": false,
"sourceMap": 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
}
})