mirror of
https://github.com/Azure/k8s-deploy.git
synced 2026-06-22 19:59:26 +08:00
Compare commits
41 Commits
create-release
...
v5.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 97a78b4bf0 | |||
| 3d107b044d | |||
| d1acc1a47b | |||
| bf768b3109 | |||
| d3b3950a9c | |||
| 4b49af4189 | |||
| 0c838316d4 | |||
| e5725dfe9f | |||
| b34f3e7f18 | |||
| 10d196d204 | |||
| df58fb461e | |||
| a999ffcd6c | |||
| 00795b0b56 | |||
| d565a17533 | |||
| 1811836de2 | |||
| 10d9433b15 | |||
| 52dfbef986 | |||
| 074d812926 | |||
| e10b599478 | |||
| 93550c22f0 | |||
| 1fea8281df | |||
| 1b1edcdfc7 | |||
| 8cbe18c310 | |||
| 8efbc8ba92 | |||
| 699a70732d | |||
| a1d061da9d | |||
| 7c36b75ebe | |||
| 2f2901757b | |||
| 4aba7c26f3 | |||
| d6508445a1 | |||
| a462095a3c | |||
| e52890db9e | |||
| dd4bbd13a5 | |||
| ecb488266d | |||
| 756cc0a511 | |||
| dcaec012e2 | |||
| 7dae909398 | |||
| e8a841df59 | |||
| da1e907ad7 | |||
| 8ce7d1dcdd | |||
| b9a9965750 |
@@ -0,0 +1,18 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
groups:
|
||||||
|
actions:
|
||||||
|
patterns:
|
||||||
|
- '*'
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: .github/workflows
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
groups:
|
||||||
|
actions:
|
||||||
|
patterns:
|
||||||
|
- '*'
|
||||||
@@ -10,23 +10,21 @@ jobs:
|
|||||||
CodeQL-Build:
|
CodeQL-Build:
|
||||||
# CodeQL runs on ubuntu-latest and windows-latest
|
# CodeQL runs on ubuntu-latest and windows-latest
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||||
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.
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
# If this run was triggered by a pull request event, then checkout
|
|
||||||
# the head of the pull request instead of the merge commit.
|
|
||||||
- run: git checkout HEAD^2
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd #v3.27.0
|
||||||
# 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
|
||||||
@@ -34,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@v1
|
uses: github/codeql-action/autobuild@662472033e021d55d94146f66f6058822b0b39fd #v3.27.0
|
||||||
|
|
||||||
# ℹ️ 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
|
||||||
@@ -48,4 +46,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd #v3.27.0
|
||||||
|
|||||||
@@ -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@v3
|
- uses: actions/stale@v9
|
||||||
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@v3
|
- uses: actions/stale@v9
|
||||||
name: Setting PR as idle
|
name: Setting PR as idle
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
- name: install deps
|
||||||
|
run: npm install
|
||||||
|
|
||||||
- name: Enforce Prettier
|
- name: Enforce Prettier
|
||||||
uses: actionsx/prettier@v2
|
run: npm run format-check
|
||||||
with:
|
|
||||||
args: --check .
|
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
name: Create release PR
|
name: Release Project
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- CHANGELOG.md
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
release:
|
|
||||||
description: 'Define release version (ex: v1, v2, v3)'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-pr:
|
release:
|
||||||
uses: OliverMKing/javascript-release-workflow/.github/workflows/release-pr.yml@main
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: write
|
||||||
|
uses: Azure/action-release-workflows/.github/workflows/release_js_project.yaml@v1
|
||||||
with:
|
with:
|
||||||
release: ${{ github.event.inputs.release }}
|
changelogPath: ./CHANGELOG.md
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: ncc build src/run.ts -o lib
|
run: ncc build src/run.ts -o lib
|
||||||
|
|
||||||
- uses: Azure/setup-kubectl@v3
|
- uses: Azure/setup-kubectl@v4
|
||||||
name: Install Kubectl
|
name: Install Kubectl
|
||||||
|
|
||||||
- id: setup-minikube
|
- id: setup-minikube
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- 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@v2
|
- uses: actions/setup-python@v5
|
||||||
name: Install Python
|
name: Install Python
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
@@ -64,9 +64,13 @@ jobs:
|
|||||||
images: nginx:1.14.2
|
images: nginx:1.14.2
|
||||||
manifests: |
|
manifests: |
|
||||||
test/integration/manifests/test.yml
|
test/integration/manifests/test.yml
|
||||||
|
test/integration/manifests/manifest_test_dir/test.yml
|
||||||
action: deploy
|
action: deploy
|
||||||
|
|
||||||
- name: Checking if deployments and services were created
|
- name: Checking if deployments and services were created
|
||||||
run: |
|
run: |
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_basic selectorLabels=app:nginx
|
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_basic selectorLabels=app:nginx
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_basic selectorLabels=app:nginx
|
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_basic selectorLabels=app:nginx
|
||||||
|
|
||||||
|
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment3 containerName=nginx:1.14.2 labels=app:nginx3,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_basic selectorLabels=app:nginx3
|
||||||
|
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service3 labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_basic selectorLabels=app:nginx3
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -31,14 +31,14 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: ncc build src/run.ts -o lib
|
run: ncc build src/run.ts -o lib
|
||||||
|
|
||||||
- uses: Azure/setup-kubectl@v3
|
- uses: Azure/setup-kubectl@v4
|
||||||
name: Install Kubectl
|
name: Install Kubectl
|
||||||
|
|
||||||
- id: setup-minikube
|
- id: setup-minikube
|
||||||
name: Setup Minikube
|
name: Setup Minikube
|
||||||
uses: medyagh/setup-minikube@latest
|
uses: medyagh/setup-minikube@latest
|
||||||
with:
|
with:
|
||||||
minikube-version: 1.24.0
|
minikube-version: 1.31.2
|
||||||
kubernetes-version: 1.22.3
|
kubernetes-version: 1.22.3
|
||||||
driver: 'none'
|
driver: 'none'
|
||||||
timeout-minutes: 3
|
timeout-minutes: 3
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- 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@v2
|
- uses: actions/setup-python@v5
|
||||||
name: Install Python
|
name: Install Python
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -31,14 +31,14 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: ncc build src/run.ts -o lib
|
run: ncc build src/run.ts -o lib
|
||||||
|
|
||||||
- uses: Azure/setup-kubectl@v3
|
- uses: Azure/setup-kubectl@v4
|
||||||
name: Install Kubectl
|
name: Install Kubectl
|
||||||
|
|
||||||
- id: setup-minikube
|
- id: setup-minikube
|
||||||
name: Setup Minikube
|
name: Setup Minikube
|
||||||
uses: medyagh/setup-minikube@latest
|
uses: medyagh/setup-minikube@latest
|
||||||
with:
|
with:
|
||||||
minikube-version: 1.24.0
|
minikube-version: 1.31.2
|
||||||
kubernetes-version: 1.22.3
|
kubernetes-version: 1.22.3
|
||||||
driver: 'none'
|
driver: 'none'
|
||||||
timeout-minutes: 3
|
timeout-minutes: 3
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- 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@v2
|
- uses: actions/setup-python@v5
|
||||||
name: Install Python
|
name: Install Python
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: ncc build src/run.ts -o lib
|
run: ncc build src/run.ts -o lib
|
||||||
|
|
||||||
- uses: Azure/setup-kubectl@v3
|
- uses: Azure/setup-kubectl@v4
|
||||||
name: Install Kubectl
|
name: Install Kubectl
|
||||||
|
|
||||||
- id: setup-minikube
|
- id: setup-minikube
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
- 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@v2
|
- uses: actions/setup-python@v5
|
||||||
name: Install Python
|
name: Install Python
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: ncc build src/run.ts -o lib
|
run: ncc build src/run.ts -o lib
|
||||||
|
|
||||||
- uses: Azure/setup-kubectl@v3
|
- uses: Azure/setup-kubectl@v4
|
||||||
name: Install Kubectl
|
name: Install Kubectl
|
||||||
|
|
||||||
- id: setup-minikube
|
- id: setup-minikube
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- 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@v2
|
- uses: actions/setup-python@v5
|
||||||
name: Install Python
|
name: Install Python
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
run-integration-test:
|
run-integration-test:
|
||||||
name: Run Minikube Integration Tests
|
name: Run Minikube Integration Tests
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
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@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: ncc build src/run.ts -o lib
|
run: ncc build src/run.ts -o lib
|
||||||
|
|
||||||
- uses: Azure/setup-kubectl@v3
|
- uses: Azure/setup-kubectl@v4
|
||||||
name: Install Kubectl
|
name: Install Kubectl
|
||||||
|
|
||||||
- id: setup-minikube
|
- id: setup-minikube
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
- 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@v2
|
- uses: actions/setup-python@v5
|
||||||
name: Install Python
|
name: Install Python
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -30,20 +30,20 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: ncc build src/run.ts -o lib
|
run: ncc build src/run.ts -o lib
|
||||||
- name: Azure login
|
- name: Azure login
|
||||||
uses: azure/login@v1.4.3
|
uses: azure/login@v2.2.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@v3
|
- uses: Azure/setup-kubectl@v4
|
||||||
name: Install Kubectl
|
name: Install Kubectl
|
||||||
|
|
||||||
- name: Create private AKS cluster and set context
|
- name: Create private AKS cluster and set context
|
||||||
run: |
|
run: |
|
||||||
set +x
|
set +x
|
||||||
# create cluster
|
# create cluster
|
||||||
az group create --location eastus --name ${{ env.NAMESPACE }}
|
az group create --location eastus2 --name ${{ env.NAMESPACE }}
|
||||||
az aks create --name ${{ env.NAMESPACE }} --resource-group ${{ env.NAMESPACE }} --enable-private-cluster --generate-ssh-keys
|
az aks create --name ${{ env.NAMESPACE }} --resource-group ${{ env.NAMESPACE }} --enable-private-cluster --generate-ssh-keys
|
||||||
az aks get-credentials --resource-group ${{ env.NAMESPACE }} --name ${{ env.NAMESPACE }}
|
az aks get-credentials --resource-group ${{ env.NAMESPACE }} --name ${{ env.NAMESPACE }}
|
||||||
|
|
||||||
@@ -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@v2
|
- uses: actions/setup-python@v5
|
||||||
name: Install Python
|
name: Install Python
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
@@ -63,6 +63,7 @@ jobs:
|
|||||||
images: nginx:1.14.2
|
images: nginx:1.14.2
|
||||||
manifests: |
|
manifests: |
|
||||||
test/integration/manifests/test.yml
|
test/integration/manifests/test.yml
|
||||||
|
test/integration/manifests/test2.yml
|
||||||
action: deploy
|
action: deploy
|
||||||
private-cluster: true
|
private-cluster: true
|
||||||
resource-group: ${{ env.NAMESPACE }}
|
resource-group: ${{ env.NAMESPACE }}
|
||||||
@@ -73,6 +74,9 @@ jobs:
|
|||||||
python test/integration/k8s-deploy-test.py private=${{ env.NAMESPACE }} namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Cluster_Integration_Tests_-_private_cluster selectorLabels=app:nginx
|
python test/integration/k8s-deploy-test.py private=${{ env.NAMESPACE }} namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Cluster_Integration_Tests_-_private_cluster selectorLabels=app:nginx
|
||||||
python test/integration/k8s-deploy-test.py private=${{ env.NAMESPACE }} namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Cluster_Integration_Tests_-_private_cluster selectorLabels=app:nginx
|
python test/integration/k8s-deploy-test.py private=${{ env.NAMESPACE }} namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Cluster_Integration_Tests_-_private_cluster selectorLabels=app:nginx
|
||||||
|
|
||||||
|
python test/integration/k8s-deploy-test.py private=${{ env.NAMESPACE }} namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment2 containerName=nginx:1.14.2 labels=app:nginx2,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Cluster_Integration_Tests_-_private_cluster selectorLabels=app:nginx2
|
||||||
|
python test/integration/k8s-deploy-test.py private=${{ env.NAMESPACE }} namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service2 labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Cluster_Integration_Tests_-_private_cluster selectorLabels=app:nginx2
|
||||||
|
|
||||||
- name: Clean up AKS cluster
|
- name: Clean up AKS cluster
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
name: Minikube Integration Tests - resource annotation
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'releases/*'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'releases/*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-integration-test:
|
||||||
|
name: Run Minikube Integration Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /home/runner/.kube/config
|
||||||
|
NAMESPACE: test-${{ github.run_id }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
rm -rf node_modules/
|
||||||
|
npm install
|
||||||
|
- name: Install ncc
|
||||||
|
run: npm i -g @vercel/ncc
|
||||||
|
- name: Install conntrack
|
||||||
|
run: sudo apt-get install -y conntrack
|
||||||
|
- name: Build
|
||||||
|
run: ncc build src/run.ts -o lib
|
||||||
|
|
||||||
|
- uses: Azure/setup-kubectl@v4
|
||||||
|
name: Install Kubectl
|
||||||
|
|
||||||
|
- id: setup-minikube
|
||||||
|
name: Setup Minikube
|
||||||
|
uses: medyagh/setup-minikube@latest
|
||||||
|
with:
|
||||||
|
minikube-version: 1.24.0
|
||||||
|
kubernetes-version: 1.22.3
|
||||||
|
driver: 'none'
|
||||||
|
timeout-minutes: 3
|
||||||
|
|
||||||
|
- name: Create namespace to run tests
|
||||||
|
run: kubectl create ns ${{ env.NAMESPACE }}
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
name: Install Python
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
|
||||||
|
- name: Cleaning any previously created items
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-delete.py 'Service' 'all' ${{ env.NAMESPACE }}
|
||||||
|
python test/integration/k8s-deploy-delete.py 'Deployment' 'all' ${{ env.NAMESPACE }}
|
||||||
|
python test/integration/k8s-deploy-delete.py 'Ingress' 'all' ${{ env.NAMESPACE }}
|
||||||
|
|
||||||
|
- name: Executing deploy action for pod with resource annotation enabled by default
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
namespace: ${{ env.NAMESPACE }}
|
||||||
|
images: nginx:1.14.2
|
||||||
|
manifests: |
|
||||||
|
test/integration/manifests/test.yml
|
||||||
|
action: deploy
|
||||||
|
|
||||||
|
- name: Checking if deployments is created with additional resource annotation
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_resource_annotation selectorLabels=app:nginx annotations=actions.github.com/k8s-deploy,deployment.kubernetes.io/revision,kubectl.kubernetes.io/last-applied-configuration
|
||||||
|
|
||||||
|
- name: Cleaning previously created deployment
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-delete.py 'Deployment' 'all' ${{ env.NAMESPACE }}
|
||||||
|
|
||||||
|
- name: Executing deploy action for pod with resource annotation disabled
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
namespace: ${{ env.NAMESPACE }}
|
||||||
|
images: nginx:1.14.2
|
||||||
|
manifests: |
|
||||||
|
test/integration/manifests/test.yml
|
||||||
|
action: deploy
|
||||||
|
annotate-resources: false
|
||||||
|
|
||||||
|
- name: Checking if deployment is created without additional resource annotation
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 selectorLabels=app:nginx annotations=deployment.kubernetes.io/revision,kubectl.kubernetes.io/last-applied-configuration
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
name: Tag and create release draft
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- releases/*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
tag-and-release:
|
|
||||||
uses: OliverMKing/javascript-release-workflow/.github/workflows/tag-and-release.yml@main
|
|
||||||
@@ -11,9 +11,10 @@ on: # rebuild any PRs and main branch changes
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build: # make sure build/ci works properly
|
build: # make sure build/ci works properly
|
||||||
|
name: Run Unit Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- run: |
|
- run: |
|
||||||
npm install
|
npm install
|
||||||
npm test
|
npm test
|
||||||
|
|||||||
@@ -2,6 +2,5 @@ node_modules
|
|||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.idea
|
.idea
|
||||||
lib/
|
|
||||||
|
|
||||||
coverage/
|
coverage/
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [5.0.1] - 2024-03-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- #356 Add fleet support
|
||||||
|
|
||||||
|
## [5.0.0] - 2024-03-12
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- #309 Updated to Node20 and upgraded release workflows to @v1 tag
|
||||||
|
- #306 update release workflow to use new prefix, remove deprecated release
|
||||||
|
- #303 fix: ensure imageNames are not empty strings
|
||||||
|
- #299 bump release workflow sha
|
||||||
|
- #298 bump minikube to fix runner deps
|
||||||
|
- #297 update release workflow
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- #304 add v prefix for version tagging
|
||||||
|
- #302 adding ncc to build
|
||||||
|
- #301 adding release workflow artifact fix
|
||||||
|
|
||||||
|
## [4.10.0] - 2023-10-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- #287 Make annotating resources optional
|
||||||
|
- #283 Fix “Service” route-method of the Blue-Green strategy with some manifest files
|
||||||
|
- #281 bump codeql to node 16
|
||||||
|
- #279 upgrade codeql
|
||||||
|
- #276 Fixes multiple namespaces bug
|
||||||
@@ -113,14 +113,22 @@ Following are the key capabilities of this action:
|
|||||||
<td>force </br></br>(Optional)</td>
|
<td>force </br></br>(Optional)</td>
|
||||||
<td>Deploy when a previous deployment already exists. If true then '--force' argument is added to the apply command. Using '--force' argument is not recommended in production.</td>
|
<td>Deploy when a previous deployment already exists. If true then '--force' argument is added to the apply command. Using '--force' argument is not recommended in production.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>annotate-resources</br></br>(Optional)</td>
|
||||||
|
<td>Acceptable values: true/false</br>Default value: true</br>Switch whether to annotate the resources or not. If set to false all annotations are skipped completely.</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>annotate-namespace</br></br>(Optional)</td>
|
<td>annotate-namespace</br></br>(Optional)</td>
|
||||||
<td>Acceptable values: true/false</br>Default value: true</br>Switch whether to annotate the namespace resources object or not</td>
|
<td>Acceptable values: true/false</br>Default value: true</br>Switch whether to annotate the namespace resources object or not. Ignored when annotate-resources is set to false.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>skip-tls-verify</br></br>(Optional)</td>
|
<td>skip-tls-verify</br></br>(Optional)</td>
|
||||||
<td>Acceptable values: true/false</br>Default value: false</br>True if the insecure-skip-tls-verify option should be used</td>
|
<td>Acceptable values: true/false</br>Default value: false</br>True if the insecure-skip-tls-verify option should be used</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>resource-type (Optional)</td>
|
||||||
|
<td>Acceptable values: `Microsoft.ContainerService/managedClusters` (default), 'Microsoft.ContainerService/fleets'</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Usage Examples
|
## Usage Examples
|
||||||
@@ -128,7 +136,7 @@ Following are the key capabilities of this action:
|
|||||||
### Basic deployment (without any deployment strategy)
|
### Basic deployment (without any deployment strategy)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: Azure/k8s-deploy@v3.1
|
- uses: Azure/k8s-deploy@v5
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
manifests: |
|
manifests: |
|
||||||
@@ -142,7 +150,7 @@ Following are the key capabilities of this action:
|
|||||||
### Private cluster deployment
|
### Private cluster deployment
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: Azure/k8s-deploy@v4
|
- uses: Azure/k8s-deploy@v5
|
||||||
with:
|
with:
|
||||||
resource-group: yourResourceGroup
|
resource-group: yourResourceGroup
|
||||||
name: yourClusterName
|
name: yourClusterName
|
||||||
@@ -162,7 +170,7 @@ Following are the key capabilities of this action:
|
|||||||
### Canary deployment without service mesh
|
### Canary deployment without service mesh
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: Azure/k8s-deploy@v3.1
|
- uses: Azure/k8s-deploy@v5
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||||
@@ -181,7 +189,7 @@ Following are the key capabilities of this action:
|
|||||||
To promote/reject the canary created by the above snippet, the following YAML snippet could be used:
|
To promote/reject the canary created by the above snippet, the following YAML snippet could be used:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: Azure/k8s-deploy@v3.1
|
- uses: Azure/k8s-deploy@v5
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||||
@@ -199,7 +207,7 @@ To promote/reject the canary created by the above snippet, the following YAML sn
|
|||||||
### Canary deployment based on Service Mesh Interface
|
### Canary deployment based on Service Mesh Interface
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: Azure/k8s-deploy@v3.1
|
- uses: Azure/k8s-deploy@v5
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||||
@@ -220,7 +228,7 @@ To promote/reject the canary created by the above snippet, the following YAML sn
|
|||||||
To promote/reject the canary created by the above snippet, the following YAML snippet could be used:
|
To promote/reject the canary created by the above snippet, the following YAML snippet could be used:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: Azure/k8s-deploy@v3.1
|
- uses: Azure/k8s-deploy@v5
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }} '
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }} '
|
||||||
@@ -239,7 +247,7 @@ To promote/reject the canary created by the above snippet, the following YAML sn
|
|||||||
### Blue-Green deployment with different route methods
|
### Blue-Green deployment with different route methods
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: Azure/k8s-deploy@v3.1
|
- uses: Azure/k8s-deploy@v5
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||||
@@ -259,7 +267,7 @@ To promote/reject the canary created by the above snippet, the following YAML sn
|
|||||||
To promote/reject the green workload created by the above snippet, the following YAML snippet could be used:
|
To promote/reject the green workload created by the above snippet, the following YAML snippet could be used:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: Azure/k8s-deploy@v3.1
|
- uses: Azure/k8s-deploy@v5
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||||
@@ -288,7 +296,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: Azure/docker-login@v1
|
- uses: Azure/docker-login@v1
|
||||||
with:
|
with:
|
||||||
@@ -300,23 +308,23 @@ jobs:
|
|||||||
docker build . -t contoso.azurecr.io/k8sdemo:${{ github.sha }}
|
docker build . -t contoso.azurecr.io/k8sdemo:${{ github.sha }}
|
||||||
docker push contoso.azurecr.io/k8sdemo:${{ github.sha }}
|
docker push contoso.azurecr.io/k8sdemo:${{ github.sha }}
|
||||||
|
|
||||||
- uses: azure/setup-kubectl@v2.0
|
- uses: azure/setup-kubectl@v4
|
||||||
|
|
||||||
# Set the target AKS cluster.
|
# Set the target AKS cluster.
|
||||||
- uses: Azure/aks-set-context@v1
|
- uses: Azure/aks-set-context@v4
|
||||||
with:
|
with:
|
||||||
creds: '${{ secrets.AZURE_CREDENTIALS }}'
|
creds: '${{ secrets.AZURE_CREDENTIALS }}'
|
||||||
cluster-name: contoso
|
cluster-name: contoso
|
||||||
resource-group: contoso-rg
|
resource-group: contoso-rg
|
||||||
|
|
||||||
- uses: Azure/k8s-create-secret@v1.1
|
- 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@v3.1
|
- uses: Azure/k8s-deploy@v5
|
||||||
with:
|
with:
|
||||||
action: deploy
|
action: deploy
|
||||||
manifests: |
|
manifests: |
|
||||||
@@ -337,7 +345,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: Azure/docker-login@v1
|
- uses: Azure/docker-login@v1
|
||||||
with:
|
with:
|
||||||
@@ -349,20 +357,20 @@ jobs:
|
|||||||
docker build . -t contoso.azurecr.io/k8sdemo:${{ github.sha }}
|
docker build . -t contoso.azurecr.io/k8sdemo:${{ github.sha }}
|
||||||
docker push contoso.azurecr.io/k8sdemo:${{ github.sha }}
|
docker push contoso.azurecr.io/k8sdemo:${{ github.sha }}
|
||||||
|
|
||||||
- uses: azure/setup-kubectl@v2.0
|
- uses: azure/setup-kubectl@v4
|
||||||
|
|
||||||
- uses: Azure/k8s-set-context@v2
|
- uses: Azure/k8s-set-context@v4
|
||||||
with:
|
with:
|
||||||
kubeconfig: ${{ secrets.KUBE_CONFIG }}
|
kubeconfig: ${{ secrets.KUBE_CONFIG }}
|
||||||
|
|
||||||
- uses: Azure/k8s-create-secret@v1.1
|
- 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@v3.1
|
- uses: Azure/k8s-deploy@v4
|
||||||
with:
|
with:
|
||||||
action: deploy
|
action: deploy
|
||||||
manifests: |
|
manifests: |
|
||||||
@@ -387,7 +395,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: Azure/docker-login@v1
|
- uses: Azure/docker-login@v1
|
||||||
with:
|
with:
|
||||||
@@ -419,16 +427,16 @@ jobs:
|
|||||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
- uses: azure/setup-kubectl@v2.0
|
- uses: azure/setup-kubectl@v4
|
||||||
|
|
||||||
# Set the target AKS cluster.
|
# Set the target AKS cluster.
|
||||||
- uses: Azure/aks-set-context@v1
|
- uses: Azure/aks-set-context@v4
|
||||||
with:
|
with:
|
||||||
creds: '${{ secrets.AZURE_CREDENTIALS }}'
|
creds: '${{ secrets.AZURE_CREDENTIALS }}'
|
||||||
cluster-name: contoso
|
cluster-name: contoso
|
||||||
resource-group: contoso-rg
|
resource-group: contoso-rg
|
||||||
|
|
||||||
- uses: Azure/k8s-create-secret@v1.1
|
- 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
|
||||||
@@ -436,7 +444,7 @@ jobs:
|
|||||||
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-bake@v2
|
- uses: azure/k8s-bake@v3
|
||||||
with:
|
with:
|
||||||
renderEngine: 'helm'
|
renderEngine: 'helm'
|
||||||
helmChart: './aks-helloworld/'
|
helmChart: './aks-helloworld/'
|
||||||
@@ -446,7 +454,7 @@ jobs:
|
|||||||
helm-version: 'latest'
|
helm-version: 'latest'
|
||||||
id: bake
|
id: bake
|
||||||
|
|
||||||
- uses: Azure/k8s-deploy@v1.2
|
- uses: Azure/k8s-deploy@v5
|
||||||
with:
|
with:
|
||||||
action: deploy
|
action: deploy
|
||||||
manifests: ${{ steps.bake.outputs.manifestsBundle }}
|
manifests: ${{ steps.bake.outputs.manifestsBundle }}
|
||||||
|
|||||||
+6
-2
@@ -59,8 +59,12 @@ inputs:
|
|||||||
description: 'Github token'
|
description: 'Github token'
|
||||||
default: ${{ github.token }}
|
default: ${{ github.token }}
|
||||||
required: true
|
required: true
|
||||||
|
annotate-resources:
|
||||||
|
description: 'Annotate the resources. If set to false all annotations are skipped completely.'
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
annotate-namespace:
|
annotate-namespace:
|
||||||
description: 'Annotate the target namespace'
|
description: 'Annotate the target namespace. Ignored when annotate-resources is set to false.'
|
||||||
required: false
|
required: false
|
||||||
default: true
|
default: true
|
||||||
private-cluster:
|
private-cluster:
|
||||||
@@ -80,5 +84,5 @@ inputs:
|
|||||||
branding:
|
branding:
|
||||||
color: 'green'
|
color: 'green'
|
||||||
runs:
|
runs:
|
||||||
using: 'node16'
|
using: 'node20'
|
||||||
main: 'lib/index.js'
|
main: 'lib/index.js'
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
['@babel/preset-env', {targets: {node: 'current'}}],
|
||||||
|
'@babel/preset-typescript'
|
||||||
|
]
|
||||||
|
}
|
||||||
+11
-2
@@ -1,11 +1,20 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
clearMocks: true,
|
|
||||||
moduleFileExtensions: ['js', 'ts'],
|
moduleFileExtensions: ['js', 'ts'],
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
testMatch: ['**/*.test.ts'],
|
testMatch: ['**/*.test.ts'],
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.ts$': 'ts-jest'
|
'\\.[jt]sx?$': 'babel-jest'
|
||||||
},
|
},
|
||||||
|
transformIgnorePatterns: [
|
||||||
|
'node_modules/(?!' +
|
||||||
|
[
|
||||||
|
'@octokit',
|
||||||
|
'universal-user-agent',
|
||||||
|
'before-after-hook',
|
||||||
|
'minimist'
|
||||||
|
].join('|') +
|
||||||
|
')'
|
||||||
|
],
|
||||||
verbose: true,
|
verbose: true,
|
||||||
testTimeout: 9000
|
testTimeout: 9000
|
||||||
}
|
}
|
||||||
|
|||||||
+17642
File diff suppressed because it is too large
Load Diff
Generated
+4953
-7612
File diff suppressed because it is too large
Load Diff
+22
-17
@@ -1,33 +1,38 @@
|
|||||||
{
|
{
|
||||||
"name": "k8s-deploy-action",
|
"name": "k8s-deploy-action",
|
||||||
"version": "0.0.0",
|
"version": "5.0.0",
|
||||||
"author": "Deepak Sattiraju",
|
"author": "Deepak Sattiraju",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npx ncc build src/run.ts -o lib",
|
"prebuild": "npm i @vercel/ncc",
|
||||||
|
"build": "ncc build src/run.ts -o lib",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"coverage": "jest --coverage=true",
|
"coverage": "jest --coverage=true",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"format-check": "prettier --check ."
|
"format-check": "prettier --check ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.9.1",
|
"@actions/core": "^1.11.1",
|
||||||
"@actions/exec": "^1.0.0",
|
"@actions/exec": "^1.0.0",
|
||||||
"@actions/io": "^1.0.0",
|
"@actions/io": "^1.1.3",
|
||||||
"@actions/tool-cache": "1.1.2",
|
"@actions/tool-cache": "2.0.1",
|
||||||
"@octokit/core": "^3.5.1",
|
"@babel/preset-env": "^7.26.0",
|
||||||
"@octokit/plugin-retry": "^3.0.9",
|
"@babel/preset-typescript": "^7.26.0",
|
||||||
"@types/minipass": "^3.1.2",
|
"@octokit/core": "^6.1.2",
|
||||||
"js-yaml": "3.13.1"
|
"@octokit/plugin-retry": "^7.1.2",
|
||||||
|
"@types/minipass": "^3.3.5",
|
||||||
|
"js-yaml": "4.1.0",
|
||||||
|
"minimist": "^1.2.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^26.0.0",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/js-yaml": "^3.12.7",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/node": "^12.20.41",
|
"@types/minimist": "^1.2.5",
|
||||||
"jest": "^26.0.0",
|
"@types/node": "^22.8.7",
|
||||||
"ncc": "^0.3.6",
|
"@vercel/ncc": "^0.38.3",
|
||||||
"prettier": "^2.7.1",
|
"jest": "^29.7.0",
|
||||||
"ts-jest": "^26.0.0",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "3.9.5"
|
"ts-jest": "^29.2.5",
|
||||||
|
"typescript": "5.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-10
@@ -13,6 +13,12 @@ import {
|
|||||||
} from '../strategyHelpers/deploymentHelper'
|
} from '../strategyHelpers/deploymentHelper'
|
||||||
import {DeploymentStrategy} from '../types/deploymentStrategy'
|
import {DeploymentStrategy} from '../types/deploymentStrategy'
|
||||||
import {parseTrafficSplitMethod} from '../types/trafficSplitMethod'
|
import {parseTrafficSplitMethod} from '../types/trafficSplitMethod'
|
||||||
|
export const ResourceTypeManagedCluster =
|
||||||
|
'Microsoft.ContainerService/managedClusters'
|
||||||
|
export const ResourceTypeFleet = 'Microsoft.ContainerService/fleets'
|
||||||
|
export type ClusterType =
|
||||||
|
| typeof ResourceTypeManagedCluster
|
||||||
|
| typeof ResourceTypeFleet
|
||||||
|
|
||||||
export async function deploy(
|
export async function deploy(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
@@ -39,13 +45,25 @@ export async function deploy(
|
|||||||
|
|
||||||
// check manifest stability
|
// check manifest stability
|
||||||
core.startGroup('Checking manifest stability')
|
core.startGroup('Checking manifest stability')
|
||||||
|
const resourceTypeInput =
|
||||||
|
core.getInput('resource-type') || ResourceTypeManagedCluster
|
||||||
const resourceTypes: Resource[] = getResources(
|
const resourceTypes: Resource[] = getResources(
|
||||||
deployedManifestFiles,
|
deployedManifestFiles,
|
||||||
models.DEPLOYMENT_TYPES.concat([
|
models.DEPLOYMENT_TYPES.concat([
|
||||||
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE
|
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
await checkManifestStability(kubectl, resourceTypes)
|
|
||||||
|
if (
|
||||||
|
resourceTypeInput !== ResourceTypeManagedCluster &&
|
||||||
|
resourceTypeInput !== ResourceTypeFleet
|
||||||
|
) {
|
||||||
|
let errMsg = `Invalid resource type: ${resourceTypeInput}. Supported resource types are: ${ResourceTypeManagedCluster} (default), ${ResourceTypeFleet}`
|
||||||
|
core.setFailed(errMsg)
|
||||||
|
throw new Error(errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
await checkManifestStability(kubectl, resourceTypes, resourceTypeInput)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
// print ingresses
|
// print ingresses
|
||||||
@@ -56,24 +74,19 @@ export async function deploy(
|
|||||||
for (const ingressResource of ingressResources) {
|
for (const ingressResource of ingressResources) {
|
||||||
await kubectl.getResource(
|
await kubectl.getResource(
|
||||||
KubernetesConstants.DiscoveryAndLoadBalancerResource.INGRESS,
|
KubernetesConstants.DiscoveryAndLoadBalancerResource.INGRESS,
|
||||||
ingressResource.name
|
ingressResource.name,
|
||||||
|
false,
|
||||||
|
ingressResource.namespace
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
// annotate resources
|
// annotate resources
|
||||||
core.startGroup('Annotating resources')
|
core.startGroup('Annotating resources')
|
||||||
let allPods
|
|
||||||
try {
|
|
||||||
allPods = JSON.parse((await kubectl.getAllPods()).stdout)
|
|
||||||
} catch (e) {
|
|
||||||
core.debug(`Unable to parse pods: ${e}`)
|
|
||||||
}
|
|
||||||
await annotateAndLabelResources(
|
await annotateAndLabelResources(
|
||||||
deployedManifestFiles,
|
deployedManifestFiles,
|
||||||
kubectl,
|
kubectl,
|
||||||
resourceTypes,
|
resourceTypes
|
||||||
allPods
|
|
||||||
)
|
)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-20
@@ -38,6 +38,7 @@ import {
|
|||||||
TrafficSplitMethod
|
TrafficSplitMethod
|
||||||
} from '../types/trafficSplitMethod'
|
} from '../types/trafficSplitMethod'
|
||||||
import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy'
|
import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy'
|
||||||
|
import {ResourceTypeFleet, ResourceTypeManagedCluster} from './deploy'
|
||||||
|
|
||||||
export async function promote(
|
export async function promote(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
@@ -129,19 +130,13 @@ async function promoteCanary(kubectl: Kubectl, manifests: string[]) {
|
|||||||
|
|
||||||
// annotate resources
|
// annotate resources
|
||||||
core.startGroup('Annotating resources')
|
core.startGroup('Annotating resources')
|
||||||
let allPods
|
|
||||||
try {
|
|
||||||
allPods = JSON.parse((await kubectl.getAllPods()).stdout)
|
|
||||||
} catch (e) {
|
|
||||||
core.debug(`Unable to parse pods: ${e}`)
|
|
||||||
}
|
|
||||||
const resources: Resource[] = getResources(
|
const resources: Resource[] = getResources(
|
||||||
filesToAnnotate,
|
filesToAnnotate,
|
||||||
models.DEPLOYMENT_TYPES.concat([
|
models.DEPLOYMENT_TYPES.concat([
|
||||||
models.DiscoveryAndLoadBalancerResource.SERVICE
|
models.DiscoveryAndLoadBalancerResource.SERVICE
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
await annotateAndLabelResources(filesToAnnotate, kubectl, resources, allPods)
|
await annotateAndLabelResources(filesToAnnotate, kubectl, resources)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +167,8 @@ async function promoteBlueGreen(kubectl: Kubectl, manifests: string[]) {
|
|||||||
|
|
||||||
// checking stability of newly created deployments
|
// checking stability of newly created deployments
|
||||||
core.startGroup('Checking manifest stability')
|
core.startGroup('Checking manifest stability')
|
||||||
|
const resourceType =
|
||||||
|
core.getInput('resource-type') || ResourceTypeManagedCluster
|
||||||
const deployedManifestFiles = deployResult.manifestFiles
|
const deployedManifestFiles = deployResult.manifestFiles
|
||||||
const resources: Resource[] = getResources(
|
const resources: Resource[] = getResources(
|
||||||
deployedManifestFiles,
|
deployedManifestFiles,
|
||||||
@@ -179,7 +176,19 @@ async function promoteBlueGreen(kubectl: Kubectl, manifests: string[]) {
|
|||||||
models.DiscoveryAndLoadBalancerResource.SERVICE
|
models.DiscoveryAndLoadBalancerResource.SERVICE
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
await KubernetesManifestUtility.checkManifestStability(kubectl, resources)
|
if (
|
||||||
|
resourceType !== ResourceTypeManagedCluster &&
|
||||||
|
resourceType !== ResourceTypeFleet
|
||||||
|
) {
|
||||||
|
const errMsg = `Invalid resource type: ${resourceType}. Supported resource types are: ${ResourceTypeManagedCluster} (default), fleet`
|
||||||
|
core.setFailed(errMsg)
|
||||||
|
throw new Error(errMsg)
|
||||||
|
}
|
||||||
|
await KubernetesManifestUtility.checkManifestStability(
|
||||||
|
kubectl,
|
||||||
|
resources,
|
||||||
|
resourceType
|
||||||
|
)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
core.startGroup(
|
core.startGroup(
|
||||||
@@ -219,17 +228,6 @@ async function promoteBlueGreen(kubectl: Kubectl, manifests: string[]) {
|
|||||||
|
|
||||||
// annotate resources
|
// annotate resources
|
||||||
core.startGroup('Annotating resources')
|
core.startGroup('Annotating resources')
|
||||||
let allPods
|
await annotateAndLabelResources(deployedManifestFiles, kubectl, resources)
|
||||||
try {
|
|
||||||
allPods = JSON.parse((await kubectl.getAllPods()).stdout)
|
|
||||||
} catch (e) {
|
|
||||||
core.debug(`Unable to parse pods: ${e}`)
|
|
||||||
}
|
|
||||||
await annotateAndLabelResources(
|
|
||||||
deployedManifestFiles,
|
|
||||||
kubectl,
|
|
||||||
resources,
|
|
||||||
allPods
|
|
||||||
)
|
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -26,9 +26,8 @@ export async function run() {
|
|||||||
.map((manifest) => manifest.trim()) // remove surrounding whitespace
|
.map((manifest) => manifest.trim()) // remove surrounding whitespace
|
||||||
.filter((manifest) => manifest.length > 0) // remove any blanks
|
.filter((manifest) => manifest.length > 0) // remove any blanks
|
||||||
|
|
||||||
const fullManifestFilePaths = await getFilesFromDirectoriesAndURLs(
|
const fullManifestFilePaths =
|
||||||
manifestFilePaths
|
await getFilesFromDirectoriesAndURLs(manifestFilePaths)
|
||||||
)
|
|
||||||
const kubectlPath = await getKubectlPath()
|
const kubectlPath = await getKubectlPath()
|
||||||
const namespace = core.getInput('namespace') || 'default'
|
const namespace = core.getInput('namespace') || 'default'
|
||||||
const isPrivateCluster =
|
const isPrivateCluster =
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ export async function deleteGreenObjects(
|
|||||||
const resourcesToDelete: K8sDeleteObject[] = toDelete.map((obj) => {
|
const resourcesToDelete: K8sDeleteObject[] = toDelete.map((obj) => {
|
||||||
return {
|
return {
|
||||||
name: getBlueGreenResourceName(obj.metadata.name, GREEN_SUFFIX),
|
name: getBlueGreenResourceName(obj.metadata.name, GREEN_SUFFIX),
|
||||||
kind: obj.kind
|
kind: obj.kind,
|
||||||
|
namespace: obj.metadata.namespace
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -66,38 +67,46 @@ export async function deleteObjects(
|
|||||||
// other common functions
|
// other common functions
|
||||||
export function getManifestObjects(filePaths: string[]): BlueGreenManifests {
|
export function getManifestObjects(filePaths: string[]): BlueGreenManifests {
|
||||||
const deploymentEntityList: K8sObject[] = []
|
const deploymentEntityList: K8sObject[] = []
|
||||||
|
const serviceEntityList: K8sObject[] = []
|
||||||
const routedServiceEntityList: K8sObject[] = []
|
const routedServiceEntityList: K8sObject[] = []
|
||||||
const unroutedServiceEntityList: K8sObject[] = []
|
const unroutedServiceEntityList: K8sObject[] = []
|
||||||
const ingressEntityList: K8sObject[] = []
|
const ingressEntityList: K8sObject[] = []
|
||||||
const otherEntitiesList: K8sObject[] = []
|
const otherEntitiesList: K8sObject[] = []
|
||||||
const serviceNameMap = new Map<string, string>()
|
const serviceNameMap = new Map<string, string>()
|
||||||
|
|
||||||
|
// Manifest objects per type. All resources should be parsed and
|
||||||
|
// organized before we can check if services are “routed” or not.
|
||||||
filePaths.forEach((filePath: string) => {
|
filePaths.forEach((filePath: string) => {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
try {
|
||||||
yaml.safeLoadAll(fileContents, (inputObject) => {
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
if (!!inputObject) {
|
yaml.loadAll(fileContents, (inputObject: any) => {
|
||||||
const kind = inputObject.kind
|
if (!!inputObject) {
|
||||||
const name = inputObject.metadata.name
|
const kind = inputObject.kind
|
||||||
|
if (isDeploymentEntity(kind)) {
|
||||||
if (isDeploymentEntity(kind)) {
|
deploymentEntityList.push(inputObject)
|
||||||
deploymentEntityList.push(inputObject)
|
} else if (isServiceEntity(kind)) {
|
||||||
} else if (isServiceEntity(kind)) {
|
serviceEntityList.push(inputObject)
|
||||||
if (isServiceRouted(inputObject, deploymentEntityList)) {
|
} else if (isIngressEntity(kind)) {
|
||||||
routedServiceEntityList.push(inputObject)
|
ingressEntityList.push(inputObject)
|
||||||
serviceNameMap.set(
|
|
||||||
name,
|
|
||||||
getBlueGreenResourceName(name, GREEN_SUFFIX)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
unroutedServiceEntityList.push(inputObject)
|
otherEntitiesList.push(inputObject)
|
||||||
}
|
}
|
||||||
} else if (isIngressEntity(kind)) {
|
|
||||||
ingressEntityList.push(inputObject)
|
|
||||||
} else {
|
|
||||||
otherEntitiesList.push(inputObject)
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
} catch (error) {
|
||||||
|
core.error(`Error processing file ${filePath}: ${error.message}`)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
serviceEntityList.forEach((inputObject: any) => {
|
||||||
|
if (isServiceRouted(inputObject, deploymentEntityList)) {
|
||||||
|
const name = inputObject.metadata.name
|
||||||
|
routedServiceEntityList.push(inputObject)
|
||||||
|
serviceNameMap.set(name, getBlueGreenResourceName(name, GREEN_SUFFIX))
|
||||||
|
} else {
|
||||||
|
unroutedServiceEntityList.push(inputObject)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -234,9 +243,10 @@ export function isServiceSelectorSubsetOfMatchLabel(
|
|||||||
export async function fetchResource(
|
export async function fetchResource(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
kind: string,
|
kind: string,
|
||||||
name: string
|
name: string,
|
||||||
|
namespace?: string
|
||||||
): Promise<K8sObject> {
|
): Promise<K8sObject> {
|
||||||
const result = await kubectl.getResource(kind, name)
|
const result = await kubectl.getResource(kind, name, false, namespace)
|
||||||
if (result == null || !!result.stderr) {
|
if (result == null || !!result.stderr) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ export async function validateIngresses(
|
|||||||
const existingIngress = await fetchResource(
|
const existingIngress = await fetchResource(
|
||||||
kubectl,
|
kubectl,
|
||||||
inputObject.kind,
|
inputObject.kind,
|
||||||
inputObject.metadata.name
|
inputObject.metadata.name,
|
||||||
|
inputObject?.metadata?.namespace
|
||||||
)
|
)
|
||||||
|
|
||||||
const isValid =
|
const isValid =
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ export async function validateServicesState(
|
|||||||
const existingService = await fetchResource(
|
const existingService = await fetchResource(
|
||||||
kubectl,
|
kubectl,
|
||||||
serviceObject.kind,
|
serviceObject.kind,
|
||||||
serviceObject.metadata.name
|
serviceObject.metadata.name,
|
||||||
|
serviceObject?.metadata?.namespace
|
||||||
)
|
)
|
||||||
|
|
||||||
let isServiceGreen =
|
let isServiceGreen =
|
||||||
|
|||||||
@@ -77,9 +77,8 @@ export async function createTrafficSplitObject(
|
|||||||
): Promise<TrafficSplitObject> {
|
): Promise<TrafficSplitObject> {
|
||||||
// cache traffic split api version
|
// cache traffic split api version
|
||||||
if (!trafficSplitAPIVersion)
|
if (!trafficSplitAPIVersion)
|
||||||
trafficSplitAPIVersion = await kubectlUtils.getTrafficSplitAPIVersion(
|
trafficSplitAPIVersion =
|
||||||
kubectl
|
await kubectlUtils.getTrafficSplitAPIVersion(kubectl)
|
||||||
)
|
|
||||||
|
|
||||||
// retrieve annotations for TS object
|
// retrieve annotations for TS object
|
||||||
const annotations = inputAnnotations
|
const annotations = inputAnnotations
|
||||||
@@ -142,7 +141,8 @@ export async function validateTrafficSplitsState(
|
|||||||
let trafficSplitObject = await fetchResource(
|
let trafficSplitObject = await fetchResource(
|
||||||
kubectl,
|
kubectl,
|
||||||
TRAFFIC_SPLIT_OBJECT,
|
TRAFFIC_SPLIT_OBJECT,
|
||||||
getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX)
|
getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX),
|
||||||
|
serviceObject?.metadata?.namespace
|
||||||
)
|
)
|
||||||
core.debug(
|
core.debug(
|
||||||
`ts object extracted was ${JSON.stringify(trafficSplitObject)}`
|
`ts object extracted was ${JSON.stringify(trafficSplitObject)}`
|
||||||
@@ -183,7 +183,8 @@ export async function cleanupSMI(
|
|||||||
serviceObject.metadata.name,
|
serviceObject.metadata.name,
|
||||||
GREEN_SUFFIX
|
GREEN_SUFFIX
|
||||||
),
|
),
|
||||||
kind: serviceObject.kind
|
kind: serviceObject.kind,
|
||||||
|
namespace: serviceObject?.metadata?.namespace
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -195,9 +195,13 @@ async function cleanUpCanary(
|
|||||||
files: string[],
|
files: string[],
|
||||||
includeServices: boolean
|
includeServices: boolean
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
const deleteObject = async function (kind, name) {
|
const deleteObject = async function (
|
||||||
|
kind: string,
|
||||||
|
name: string,
|
||||||
|
namespace: string | undefined
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const result = await kubectl.delete([kind, name])
|
const result = await kubectl.delete([kind, name], namespace)
|
||||||
checkForErrors([result])
|
checkForErrors([result])
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
// Ignore failures of delete if it doesn't exist
|
// Ignore failures of delete if it doesn't exist
|
||||||
@@ -207,24 +211,31 @@ async function cleanUpCanary(
|
|||||||
const deletedFiles: string[] = []
|
const deletedFiles: string[] = []
|
||||||
|
|
||||||
for (const filePath of files) {
|
for (const filePath of files) {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
try {
|
||||||
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
|
|
||||||
const parsedYaml = yaml.safeLoadAll(fileContents)
|
const parsedYaml: any[] = yaml.loadAll(fileContents)
|
||||||
for (const inputObject of parsedYaml) {
|
for (const inputObject of parsedYaml) {
|
||||||
const name = inputObject.metadata.name
|
const name = inputObject.metadata.name
|
||||||
const kind = inputObject.kind
|
const kind = inputObject.kind
|
||||||
|
const namespace: string | undefined =
|
||||||
|
inputObject?.metadata?.namespace
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isDeploymentEntity(kind) ||
|
isDeploymentEntity(kind) ||
|
||||||
(includeServices && isServiceEntity(kind))
|
(includeServices && isServiceEntity(kind))
|
||||||
) {
|
) {
|
||||||
deletedFiles.push(filePath)
|
deletedFiles.push(filePath)
|
||||||
const canaryObjectName = getCanaryResourceName(name)
|
const canaryObjectName = getCanaryResourceName(name)
|
||||||
const baselineObjectName = getBaselineResourceName(name)
|
const baselineObjectName = getBaselineResourceName(name)
|
||||||
|
|
||||||
await deleteObject(kind, canaryObjectName)
|
await deleteObject(kind, canaryObjectName, namespace)
|
||||||
await deleteObject(kind, baselineObjectName)
|
await deleteObject(kind, baselineObjectName, namespace)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
core.error(`Failed to process file ${filePath}: ${error.message}`)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import * as canaryDeploymentHelper from './canaryHelper'
|
|||||||
import {isDeploymentEntity} from '../../types/kubernetesTypes'
|
import {isDeploymentEntity} from '../../types/kubernetesTypes'
|
||||||
import {getReplicaCount} from '../../utilities/manifestUpdateUtils'
|
import {getReplicaCount} from '../../utilities/manifestUpdateUtils'
|
||||||
import {DeployResult} from '../../types/deployResult'
|
import {DeployResult} from '../../types/deployResult'
|
||||||
|
import {K8sObject} from '../../types/k8sObject'
|
||||||
|
|
||||||
export async function deployPodCanary(
|
export async function deployPodCanary(
|
||||||
filePaths: string[],
|
filePaths: string[],
|
||||||
@@ -21,50 +22,73 @@ export async function deployPodCanary(
|
|||||||
throw Error('Percentage must be between 0 and 100')
|
throw Error('Percentage must be between 0 and 100')
|
||||||
|
|
||||||
for (const filePath of filePaths) {
|
for (const filePath of filePaths) {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
try {
|
||||||
const parsedYaml = yaml.safeLoadAll(fileContents)
|
const fileContents = fs.readFileSync(filePath, 'utf8')
|
||||||
for (const inputObject of parsedYaml) {
|
const parsedYaml = yaml.loadAll(fileContents)
|
||||||
const name = inputObject.metadata.name
|
for (const inputObject of parsedYaml) {
|
||||||
const kind = inputObject.kind
|
if (
|
||||||
|
inputObject &&
|
||||||
|
typeof inputObject === 'object' &&
|
||||||
|
'metadata' in inputObject &&
|
||||||
|
'kind' in inputObject &&
|
||||||
|
'spec' in inputObject &&
|
||||||
|
typeof inputObject.metadata === 'object' &&
|
||||||
|
'name' in inputObject.metadata &&
|
||||||
|
typeof inputObject.metadata.name === 'string' &&
|
||||||
|
typeof inputObject.kind === 'string'
|
||||||
|
) {
|
||||||
|
const obj = inputObject as K8sObject
|
||||||
|
const name = obj.metadata.name
|
||||||
|
const kind = obj.kind
|
||||||
|
|
||||||
if (!onlyDeployStable && isDeploymentEntity(kind)) {
|
if (!onlyDeployStable && isDeploymentEntity(kind)) {
|
||||||
core.debug('Calculating replica count for canary')
|
core.debug('Calculating replica count for canary')
|
||||||
const canaryReplicaCount = calculateReplicaCountForCanary(
|
const canaryReplicaCount = calculateReplicaCountForCanary(
|
||||||
inputObject,
|
obj,
|
||||||
percentage
|
percentage
|
||||||
)
|
|
||||||
core.debug('Replica count is ' + canaryReplicaCount)
|
|
||||||
|
|
||||||
const newCanaryObject = canaryDeploymentHelper.getNewCanaryResource(
|
|
||||||
inputObject,
|
|
||||||
canaryReplicaCount
|
|
||||||
)
|
|
||||||
newObjectsList.push(newCanaryObject)
|
|
||||||
|
|
||||||
// if there's already a stable object, deploy baseline as well
|
|
||||||
const stableObject = await canaryDeploymentHelper.fetchResource(
|
|
||||||
kubectl,
|
|
||||||
kind,
|
|
||||||
name
|
|
||||||
)
|
|
||||||
if (stableObject) {
|
|
||||||
core.debug(
|
|
||||||
`Stable object found for ${kind} ${name}. Creating baseline objects`
|
|
||||||
)
|
|
||||||
const newBaselineObject =
|
|
||||||
canaryDeploymentHelper.getNewBaselineResource(
|
|
||||||
stableObject,
|
|
||||||
canaryReplicaCount
|
|
||||||
)
|
)
|
||||||
core.debug(
|
core.debug('Replica count is ' + canaryReplicaCount)
|
||||||
'New baseline object: ' + JSON.stringify(newBaselineObject)
|
|
||||||
)
|
const newCanaryObject =
|
||||||
newObjectsList.push(newBaselineObject)
|
canaryDeploymentHelper.getNewCanaryResource(
|
||||||
|
obj,
|
||||||
|
canaryReplicaCount
|
||||||
|
)
|
||||||
|
newObjectsList.push(newCanaryObject)
|
||||||
|
|
||||||
|
// if there's already a stable object, deploy baseline as well
|
||||||
|
const stableObject =
|
||||||
|
await canaryDeploymentHelper.fetchResource(
|
||||||
|
kubectl,
|
||||||
|
kind,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
if (stableObject) {
|
||||||
|
core.debug(
|
||||||
|
`Stable object found for ${kind} ${name}. Creating baseline objects`
|
||||||
|
)
|
||||||
|
const newBaselineObject =
|
||||||
|
canaryDeploymentHelper.getNewBaselineResource(
|
||||||
|
stableObject,
|
||||||
|
canaryReplicaCount
|
||||||
|
)
|
||||||
|
core.debug(
|
||||||
|
'New baseline object: ' +
|
||||||
|
JSON.stringify(newBaselineObject)
|
||||||
|
)
|
||||||
|
newObjectsList.push(newBaselineObject)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// deploy non deployment entity or regular deployments for promote as they are
|
||||||
|
newObjectsList.push(obj)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// deploy non deployment entity or regular deployments for promote as they are
|
|
||||||
newObjectsList.push(inputObject)
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
core.error(
|
||||||
|
`Failed to parse YAML file at ${filePath}: ${error.message}`
|
||||||
|
)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {isDeploymentEntity, isServiceEntity} from '../../types/kubernetesTypes'
|
|||||||
import {checkForErrors} from '../../utilities/kubectlUtils'
|
import {checkForErrors} from '../../utilities/kubectlUtils'
|
||||||
import {inputAnnotations} from '../../inputUtils'
|
import {inputAnnotations} from '../../inputUtils'
|
||||||
import {DeployResult} from '../../types/deployResult'
|
import {DeployResult} from '../../types/deployResult'
|
||||||
|
import {K8sObject} from '../../types/k8sObject'
|
||||||
|
|
||||||
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'
|
||||||
@@ -36,60 +37,68 @@ export async function deploySMICanary(
|
|||||||
|
|
||||||
const newObjectsList = []
|
const newObjectsList = []
|
||||||
for await (const filePath of filePaths) {
|
for await (const filePath of filePaths) {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
try {
|
||||||
const inputObjects = yaml.safeLoadAll(fileContents)
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
for (const inputObject of inputObjects) {
|
const inputObjects: K8sObject[] = yaml.loadAll(
|
||||||
const name = inputObject.metadata.name
|
fileContents
|
||||||
const kind = inputObject.kind
|
) as K8sObject[]
|
||||||
|
for (const inputObject of inputObjects) {
|
||||||
|
const name = inputObject.metadata.name
|
||||||
|
const kind = inputObject.kind
|
||||||
|
|
||||||
if (!onlyDeployStable && isDeploymentEntity(kind)) {
|
if (!onlyDeployStable && isDeploymentEntity(kind)) {
|
||||||
if (calculateReplicas) {
|
if (calculateReplicas) {
|
||||||
// calculate for each object
|
// calculate for each object
|
||||||
const percentage = parseInt(
|
const percentage = parseInt(
|
||||||
core.getInput('percentage', {required: true})
|
core.getInput('percentage', {required: true})
|
||||||
)
|
|
||||||
canaryReplicaCount =
|
|
||||||
podCanaryHelper.calculateReplicaCountForCanary(
|
|
||||||
inputObject,
|
|
||||||
percentage
|
|
||||||
)
|
)
|
||||||
core.debug(`calculated replica count ${canaryReplicaCount}`)
|
canaryReplicaCount =
|
||||||
}
|
podCanaryHelper.calculateReplicaCountForCanary(
|
||||||
|
inputObject,
|
||||||
|
percentage
|
||||||
|
)
|
||||||
|
core.debug(`calculated replica count ${canaryReplicaCount}`)
|
||||||
|
}
|
||||||
|
|
||||||
core.debug('Creating canary object')
|
core.debug('Creating canary object')
|
||||||
const newCanaryObject = canaryDeploymentHelper.getNewCanaryResource(
|
const newCanaryObject =
|
||||||
inputObject,
|
canaryDeploymentHelper.getNewCanaryResource(
|
||||||
canaryReplicaCount
|
inputObject,
|
||||||
)
|
|
||||||
newObjectsList.push(newCanaryObject)
|
|
||||||
|
|
||||||
const stableObject = await canaryDeploymentHelper.fetchResource(
|
|
||||||
kubectl,
|
|
||||||
kind,
|
|
||||||
canaryDeploymentHelper.getStableResourceName(name)
|
|
||||||
)
|
|
||||||
if (stableObject) {
|
|
||||||
core.debug(
|
|
||||||
`Stable object found for ${kind} ${name}. Creating baseline objects`
|
|
||||||
)
|
|
||||||
const newBaselineObject =
|
|
||||||
canaryDeploymentHelper.getBaselineDeploymentFromStableDeployment(
|
|
||||||
stableObject,
|
|
||||||
canaryReplicaCount
|
canaryReplicaCount
|
||||||
)
|
)
|
||||||
newObjectsList.push(newBaselineObject)
|
newObjectsList.push(newCanaryObject)
|
||||||
|
|
||||||
|
const stableObject = await canaryDeploymentHelper.fetchResource(
|
||||||
|
kubectl,
|
||||||
|
kind,
|
||||||
|
canaryDeploymentHelper.getStableResourceName(name)
|
||||||
|
)
|
||||||
|
if (stableObject) {
|
||||||
|
core.debug(
|
||||||
|
`Stable object found for ${kind} ${name}. Creating baseline objects`
|
||||||
|
)
|
||||||
|
const newBaselineObject =
|
||||||
|
canaryDeploymentHelper.getBaselineDeploymentFromStableDeployment(
|
||||||
|
stableObject,
|
||||||
|
canaryReplicaCount
|
||||||
|
)
|
||||||
|
newObjectsList.push(newBaselineObject)
|
||||||
|
}
|
||||||
|
} else if (isDeploymentEntity(kind)) {
|
||||||
|
core.debug(
|
||||||
|
`creating stable deployment with ${inputObject.spec.replicas} replicas`
|
||||||
|
)
|
||||||
|
const stableDeployment =
|
||||||
|
canaryDeploymentHelper.getStableResource(inputObject)
|
||||||
|
newObjectsList.push(stableDeployment)
|
||||||
|
} else {
|
||||||
|
// Update non deployment entity or stable deployment as it is
|
||||||
|
newObjectsList.push(inputObject)
|
||||||
}
|
}
|
||||||
} else if (isDeploymentEntity(kind)) {
|
|
||||||
core.debug(
|
|
||||||
`creating stable deployment with ${inputObject.spec.replicas} replicas`
|
|
||||||
)
|
|
||||||
const stableDeployment =
|
|
||||||
canaryDeploymentHelper.getStableResource(inputObject)
|
|
||||||
newObjectsList.push(stableDeployment)
|
|
||||||
} else {
|
|
||||||
// Update non deployment entity or stable deployment as it is
|
|
||||||
newObjectsList.push(inputObject)
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
core.error(`Failed to process file at ${filePath}: ${error.message}`)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
core.debug(
|
core.debug(
|
||||||
@@ -111,81 +120,90 @@ async function createCanaryService(
|
|||||||
const trafficObjectsList: string[] = []
|
const trafficObjectsList: string[] = []
|
||||||
|
|
||||||
for (const filePath of filePaths) {
|
for (const filePath of filePaths) {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
try {
|
||||||
const parsedYaml = yaml.safeLoadAll(fileContents)
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
for (const inputObject of parsedYaml) {
|
const parsedYaml: K8sObject[] = yaml.loadAll(
|
||||||
const name = inputObject.metadata.name
|
fileContents
|
||||||
const kind = inputObject.kind
|
) as K8sObject[]
|
||||||
|
|
||||||
if (isServiceEntity(kind)) {
|
for (const inputObject of parsedYaml) {
|
||||||
core.debug(`Creating services for ${kind} ${name}`)
|
const name = inputObject.metadata.name
|
||||||
const newCanaryServiceObject =
|
const kind = inputObject.kind
|
||||||
canaryDeploymentHelper.getNewCanaryResource(inputObject)
|
|
||||||
newObjectsList.push(newCanaryServiceObject)
|
|
||||||
|
|
||||||
const newBaselineServiceObject =
|
if (isServiceEntity(kind)) {
|
||||||
canaryDeploymentHelper.getNewBaselineResource(inputObject)
|
core.debug(`Creating services for ${kind} ${name}`)
|
||||||
newObjectsList.push(newBaselineServiceObject)
|
const newCanaryServiceObject =
|
||||||
|
canaryDeploymentHelper.getNewCanaryResource(inputObject)
|
||||||
|
newObjectsList.push(newCanaryServiceObject)
|
||||||
|
|
||||||
const stableObject = await canaryDeploymentHelper.fetchResource(
|
const newBaselineServiceObject =
|
||||||
kubectl,
|
canaryDeploymentHelper.getNewBaselineResource(inputObject)
|
||||||
kind,
|
newObjectsList.push(newBaselineServiceObject)
|
||||||
canaryDeploymentHelper.getStableResourceName(name)
|
|
||||||
)
|
|
||||||
if (!stableObject) {
|
|
||||||
const newStableServiceObject =
|
|
||||||
canaryDeploymentHelper.getStableResource(inputObject)
|
|
||||||
newObjectsList.push(newStableServiceObject)
|
|
||||||
|
|
||||||
core.debug('Creating the traffic object for service: ' + name)
|
const stableObject = await canaryDeploymentHelper.fetchResource(
|
||||||
const trafficObject = await createTrafficSplitManifestFile(
|
|
||||||
kubectl,
|
kubectl,
|
||||||
name,
|
kind,
|
||||||
0,
|
canaryDeploymentHelper.getStableResourceName(name)
|
||||||
0,
|
|
||||||
1000
|
|
||||||
)
|
)
|
||||||
|
if (!stableObject) {
|
||||||
|
const newStableServiceObject =
|
||||||
|
canaryDeploymentHelper.getStableResource(inputObject)
|
||||||
|
newObjectsList.push(newStableServiceObject)
|
||||||
|
|
||||||
trafficObjectsList.push(trafficObject)
|
core.debug('Creating the traffic object for service: ' + name)
|
||||||
} else {
|
const trafficObject = await createTrafficSplitManifestFile(
|
||||||
let updateTrafficObject = true
|
kubectl,
|
||||||
const trafficObject = await canaryDeploymentHelper.fetchResource(
|
name,
|
||||||
kubectl,
|
0,
|
||||||
TRAFFIC_SPLIT_OBJECT,
|
0,
|
||||||
getTrafficSplitResourceName(name)
|
1000
|
||||||
)
|
|
||||||
|
|
||||||
if (trafficObject) {
|
|
||||||
const trafficJObject = JSON.parse(
|
|
||||||
JSON.stringify(trafficObject)
|
|
||||||
)
|
)
|
||||||
if (trafficJObject?.spec?.backends) {
|
|
||||||
trafficJObject.spec.backends.forEach((s) => {
|
trafficObjectsList.push(trafficObject)
|
||||||
if (
|
} else {
|
||||||
s.service ===
|
let updateTrafficObject = true
|
||||||
canaryDeploymentHelper.getCanaryResourceName(
|
const trafficObject =
|
||||||
name
|
await canaryDeploymentHelper.fetchResource(
|
||||||
) &&
|
kubectl,
|
||||||
s.weight === '1000m'
|
TRAFFIC_SPLIT_OBJECT,
|
||||||
) {
|
getTrafficSplitResourceName(name)
|
||||||
core.debug('Update traffic objcet not required')
|
)
|
||||||
updateTrafficObject = false
|
|
||||||
}
|
if (trafficObject) {
|
||||||
})
|
const trafficJObject = JSON.parse(
|
||||||
|
JSON.stringify(trafficObject)
|
||||||
|
)
|
||||||
|
if (trafficJObject?.spec?.backends) {
|
||||||
|
trafficJObject.spec.backends.forEach((s) => {
|
||||||
|
if (
|
||||||
|
s.service ===
|
||||||
|
canaryDeploymentHelper.getCanaryResourceName(
|
||||||
|
name
|
||||||
|
) &&
|
||||||
|
s.weight === '1000m'
|
||||||
|
) {
|
||||||
|
core.debug('Update traffic objcet not required')
|
||||||
|
updateTrafficObject = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (updateTrafficObject) {
|
if (updateTrafficObject) {
|
||||||
core.debug(
|
core.debug(
|
||||||
'Stable service object present so updating the traffic object for service: ' +
|
'Stable service object present so updating the traffic object for service: ' +
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
trafficObjectsList.push(
|
trafficObjectsList.push(
|
||||||
await updateTrafficSplitObject(kubectl, name)
|
await updateTrafficSplitObject(kubectl, name)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
core.error(`Failed to process file at ${filePath}: ${error.message}`)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,23 +242,31 @@ async function adjustTraffic(
|
|||||||
|
|
||||||
const trafficSplitManifests = []
|
const trafficSplitManifests = []
|
||||||
for (const filePath of manifestFilePaths) {
|
for (const filePath of manifestFilePaths) {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
try {
|
||||||
const parsedYaml = yaml.safeLoadAll(fileContents)
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
for (const inputObject of parsedYaml) {
|
const parsedYaml: K8sObject[] = yaml.loadAll(
|
||||||
const name = inputObject.metadata.name
|
fileContents
|
||||||
const kind = inputObject.kind
|
) as K8sObject[]
|
||||||
|
|
||||||
if (isServiceEntity(kind)) {
|
for (const inputObject of parsedYaml) {
|
||||||
trafficSplitManifests.push(
|
const name = inputObject.metadata.name
|
||||||
await createTrafficSplitManifestFile(
|
const kind = inputObject.kind
|
||||||
kubectl,
|
|
||||||
name,
|
if (isServiceEntity(kind)) {
|
||||||
stableWeight,
|
trafficSplitManifests.push(
|
||||||
0,
|
await createTrafficSplitManifestFile(
|
||||||
canaryWeight
|
kubectl,
|
||||||
|
name,
|
||||||
|
stableWeight,
|
||||||
|
0,
|
||||||
|
canaryWeight
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
core.error(`Failed to process file at ${filePath}: ${error.message}`)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,9 +347,8 @@ async function getTrafficSplitObject(
|
|||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// cached version
|
// cached version
|
||||||
if (!trafficSplitAPIVersion) {
|
if (!trafficSplitAPIVersion) {
|
||||||
trafficSplitAPIVersion = await kubectlUtils.getTrafficSplitAPIVersion(
|
trafficSplitAPIVersion =
|
||||||
kubectl
|
await kubectlUtils.getTrafficSplitAPIVersion(kubectl)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
|
|||||||
@@ -10,12 +10,7 @@ import {Kubectl, Resource} from '../types/kubectl'
|
|||||||
import {deployPodCanary} from './canary/podCanaryHelper'
|
import {deployPodCanary} from './canary/podCanaryHelper'
|
||||||
import {deploySMICanary} from './canary/smiCanaryHelper'
|
import {deploySMICanary} from './canary/smiCanaryHelper'
|
||||||
import {DeploymentConfig} from '../types/deploymentConfig'
|
import {DeploymentConfig} from '../types/deploymentConfig'
|
||||||
import {
|
import {deployBlueGreen} from './blueGreen/deploy'
|
||||||
deployBlueGreen,
|
|
||||||
deployBlueGreenIngress,
|
|
||||||
deployBlueGreenService
|
|
||||||
} from './blueGreen/deploy'
|
|
||||||
import {deployBlueGreenSMI} from './blueGreen/deploy'
|
|
||||||
import {DeploymentStrategy} from '../types/deploymentStrategy'
|
import {DeploymentStrategy} from '../types/deploymentStrategy'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {
|
import {
|
||||||
@@ -39,8 +34,8 @@ import {
|
|||||||
normalizeWorkflowStrLabel
|
normalizeWorkflowStrLabel
|
||||||
} from '../utilities/githubUtils'
|
} from '../utilities/githubUtils'
|
||||||
import {getDeploymentConfig} from '../utilities/dockerUtils'
|
import {getDeploymentConfig} from '../utilities/dockerUtils'
|
||||||
import {deploy} from '../actions/deploy'
|
|
||||||
import {DeployResult} from '../types/deployResult'
|
import {DeployResult} from '../types/deployResult'
|
||||||
|
import {ClusterType} from '../actions/deploy'
|
||||||
|
|
||||||
export async function deployManifests(
|
export async function deployManifests(
|
||||||
files: string[],
|
files: string[],
|
||||||
@@ -116,19 +111,24 @@ function appendStableVersionLabelToResource(files: string[]): string[] {
|
|||||||
const newObjectsList = []
|
const newObjectsList = []
|
||||||
|
|
||||||
files.forEach((filePath: string) => {
|
files.forEach((filePath: string) => {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
try {
|
||||||
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
|
|
||||||
yaml.safeLoadAll(fileContents, function (inputObject) {
|
yaml.loadAll(fileContents, function (inputObject) {
|
||||||
const {kind} = inputObject
|
const kind = (inputObject as {kind: string}).kind
|
||||||
|
|
||||||
if (isDeploymentEntity(kind)) {
|
if (isDeploymentEntity(kind)) {
|
||||||
const updatedObject =
|
const updatedObject =
|
||||||
canaryDeploymentHelper.markResourceAsStable(inputObject)
|
canaryDeploymentHelper.markResourceAsStable(inputObject)
|
||||||
newObjectsList.push(updatedObject)
|
newObjectsList.push(updatedObject)
|
||||||
} else {
|
} else {
|
||||||
manifestFiles.push(filePath)
|
manifestFiles.push(filePath)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} catch (error) {
|
||||||
|
core.error(`Failed to parse file at ${filePath}: ${error.message}`)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const updatedManifestFiles = fileHelper.writeObjectsToFile(newObjectsList)
|
const updatedManifestFiles = fileHelper.writeObjectsToFile(newObjectsList)
|
||||||
@@ -139,40 +139,58 @@ function appendStableVersionLabelToResource(files: string[]): string[] {
|
|||||||
|
|
||||||
export async function checkManifestStability(
|
export async function checkManifestStability(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
resources: Resource[]
|
resources: Resource[],
|
||||||
|
resourceType: ClusterType
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await KubernetesManifestUtility.checkManifestStability(kubectl, resources)
|
await KubernetesManifestUtility.checkManifestStability(
|
||||||
|
kubectl,
|
||||||
|
resources,
|
||||||
|
resourceType
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function annotateAndLabelResources(
|
export async function annotateAndLabelResources(
|
||||||
files: string[],
|
files: string[],
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
resourceTypes: Resource[],
|
resourceTypes: Resource[]
|
||||||
allPods: any
|
|
||||||
) {
|
) {
|
||||||
|
const defaultWorkflowFileName = 'k8s-deploy-failed-workflow-annotation'
|
||||||
const githubToken = core.getInput('token')
|
const githubToken = core.getInput('token')
|
||||||
const workflowFilePath = await getWorkflowFilePath(githubToken)
|
let workflowFilePath
|
||||||
|
try {
|
||||||
|
workflowFilePath = await getWorkflowFilePath(githubToken)
|
||||||
|
} catch (ex) {
|
||||||
|
core.warning(`Failed to extract workflow file name: ${ex}`)
|
||||||
|
workflowFilePath = defaultWorkflowFileName
|
||||||
|
}
|
||||||
|
|
||||||
const deploymentConfig = await getDeploymentConfig()
|
const deploymentConfig = await getDeploymentConfig()
|
||||||
const annotationKeyLabel = getWorkflowAnnotationKeyLabel()
|
const annotationKeyLabel = getWorkflowAnnotationKeyLabel()
|
||||||
|
|
||||||
await annotateResources(
|
const shouldAnnotateResources = !(
|
||||||
files,
|
core.getInput('annotate-resources').toLowerCase() === 'false'
|
||||||
kubectl,
|
)
|
||||||
resourceTypes,
|
|
||||||
allPods,
|
if (shouldAnnotateResources) {
|
||||||
annotationKeyLabel,
|
await annotateResources(
|
||||||
workflowFilePath,
|
files,
|
||||||
deploymentConfig
|
kubectl,
|
||||||
|
resourceTypes,
|
||||||
|
annotationKeyLabel,
|
||||||
|
workflowFilePath,
|
||||||
|
deploymentConfig
|
||||||
|
).catch((err) => core.warning(`Failed to annotate resources: ${err} `))
|
||||||
|
}
|
||||||
|
|
||||||
|
await labelResources(files, kubectl, annotationKeyLabel).catch((err) =>
|
||||||
|
core.warning(`Failed to label resources: ${err}`)
|
||||||
)
|
)
|
||||||
await labelResources(files, kubectl, annotationKeyLabel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function annotateResources(
|
async function annotateResources(
|
||||||
files: string[],
|
files: string[],
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
resourceTypes: Resource[],
|
resourceTypes: Resource[],
|
||||||
allPods: any,
|
|
||||||
annotationKey: string,
|
annotationKey: string,
|
||||||
workflowFilePath: string,
|
workflowFilePath: string,
|
||||||
deploymentConfig: DeploymentConfig
|
deploymentConfig: DeploymentConfig
|
||||||
@@ -186,14 +204,19 @@ async function annotateResources(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (core.isDebug()) {
|
if (core.isDebug()) {
|
||||||
core.debug(`files getting annotated are ${JSON.stringify(files)}`)
|
try {
|
||||||
for (const filePath of files) {
|
core.debug(`files getting annotated are ${JSON.stringify(files)}`)
|
||||||
core.debug('printing objects getting annotated...')
|
for (const filePath of files) {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
core.debug('printing objects getting annotated...')
|
||||||
const inputObjects = yaml.safeLoadAll(fileContents)
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
for (const inputObject of inputObjects) {
|
const inputObjects = yaml.loadAll(fileContents)
|
||||||
core.debug(`object: ${JSON.stringify(inputObject)}`)
|
for (const inputObject of inputObjects) {
|
||||||
|
core.debug(`object: ${JSON.stringify(inputObject)}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
core.error(`Failed to load and parse files: ${error.message}`)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,14 +231,21 @@ async function annotateResources(
|
|||||||
)
|
)
|
||||||
if (annotateNamespace) {
|
if (annotateNamespace) {
|
||||||
annotateResults.push(
|
annotateResults.push(
|
||||||
await kubectl.annotate('namespace', namespace, annotationKeyValStr)
|
await kubectl.annotate(
|
||||||
|
'namespace',
|
||||||
|
namespace,
|
||||||
|
annotationKeyValStr,
|
||||||
|
namespace
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
try {
|
try {
|
||||||
const annotateResult = await kubectl.annotateFiles(
|
const annotateResult = await kubectl.annotateFiles(
|
||||||
file,
|
file,
|
||||||
annotationKeyValStr
|
annotationKeyValStr,
|
||||||
|
namespace
|
||||||
)
|
)
|
||||||
annotateResults.push(annotateResult)
|
annotateResults.push(annotateResult)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -233,8 +263,8 @@ async function annotateResources(
|
|||||||
kubectl,
|
kubectl,
|
||||||
resource.type,
|
resource.type,
|
||||||
resource.name,
|
resource.name,
|
||||||
annotationKeyValStr,
|
resource.namespace,
|
||||||
allPods
|
annotationKeyValStr
|
||||||
)
|
)
|
||||||
).forEach((execResult) => annotateResults.push(execResult))
|
).forEach((execResult) => annotateResults.push(execResult))
|
||||||
}
|
}
|
||||||
@@ -258,7 +288,7 @@ async function labelResources(
|
|||||||
const labelResults = []
|
const labelResults = []
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
try {
|
try {
|
||||||
const labelResult = await kubectl.labelFiles(files, labels)
|
const labelResult = await kubectl.labelFiles(file, labels)
|
||||||
labelResults.push(labelResult)
|
labelResults.push(labelResult)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
core.warning(`failed to annotate resource: ${e}`)
|
core.warning(`failed to annotate resource: ${e}`)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export interface K8sObject {
|
|||||||
metadata: {
|
metadata: {
|
||||||
name: string
|
name: string
|
||||||
labels: Map<string, string>
|
labels: Map<string, string>
|
||||||
|
namespace?: string
|
||||||
}
|
}
|
||||||
kind: string
|
kind: string
|
||||||
spec: any
|
spec: any
|
||||||
@@ -16,6 +17,7 @@ export interface K8sServiceObject extends K8sObject {
|
|||||||
export interface K8sDeleteObject {
|
export interface K8sDeleteObject {
|
||||||
name: string
|
name: string
|
||||||
kind: string
|
kind: string
|
||||||
|
namespace?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface K8sIngress extends K8sObject {
|
export interface K8sIngress extends K8sObject {
|
||||||
|
|||||||
+176
-12
@@ -3,7 +3,6 @@ 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'
|
||||||
import * as toolCache from '@actions/tool-cache'
|
import * as toolCache from '@actions/tool-cache'
|
||||||
import {config} from 'process'
|
|
||||||
|
|
||||||
describe('Kubectl path', () => {
|
describe('Kubectl path', () => {
|
||||||
const version = '1.1'
|
const version = '1.1'
|
||||||
@@ -38,18 +37,8 @@ describe('Kubectl path', () => {
|
|||||||
const kubectlPath = 'kubectlPath'
|
const kubectlPath = 'kubectlPath'
|
||||||
const testNamespace = 'testNamespace'
|
const testNamespace = 'testNamespace'
|
||||||
const defaultNamespace = 'default'
|
const defaultNamespace = 'default'
|
||||||
|
const otherNamespace = 'otherns'
|
||||||
describe('Kubectl class', () => {
|
describe('Kubectl class', () => {
|
||||||
describe('default namespace behavior', () => {
|
|
||||||
const kubectl = new Kubectl(kubectlPath, defaultNamespace)
|
|
||||||
const execReturn = {exitCode: 0, stdout: 'Output', stderr: ''}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
|
|
||||||
return execReturn
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with a success exec return in testNamespace', () => {
|
describe('with a success exec return in testNamespace', () => {
|
||||||
const kubectl = new Kubectl(kubectlPath, testNamespace)
|
const kubectl = new Kubectl(kubectlPath, testNamespace)
|
||||||
const execReturn = {exitCode: 0, stdout: 'Output', stderr: ''}
|
const execReturn = {exitCode: 0, stdout: 'Output', stderr: ''}
|
||||||
@@ -122,6 +111,26 @@ describe('Kubectl class', () => {
|
|||||||
],
|
],
|
||||||
{silent: false}
|
{silent: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// overrided ns
|
||||||
|
const silent = false
|
||||||
|
await kubectl.describe(
|
||||||
|
resourceType,
|
||||||
|
resourceName,
|
||||||
|
silent,
|
||||||
|
otherNamespace
|
||||||
|
)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(
|
||||||
|
kubectlPath,
|
||||||
|
[
|
||||||
|
'describe',
|
||||||
|
resourceType,
|
||||||
|
resourceName,
|
||||||
|
'--namespace',
|
||||||
|
otherNamespace
|
||||||
|
],
|
||||||
|
{silent}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('describes a resource silently', async () => {
|
it('describes a resource silently', async () => {
|
||||||
@@ -140,6 +149,26 @@ describe('Kubectl class', () => {
|
|||||||
],
|
],
|
||||||
{silent: true}
|
{silent: true}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// overrided ns
|
||||||
|
const silent = false
|
||||||
|
await kubectl.describe(
|
||||||
|
resourceType,
|
||||||
|
resourceName,
|
||||||
|
silent,
|
||||||
|
otherNamespace
|
||||||
|
)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(
|
||||||
|
kubectlPath,
|
||||||
|
[
|
||||||
|
'describe',
|
||||||
|
resourceType,
|
||||||
|
resourceName,
|
||||||
|
'--namespace',
|
||||||
|
otherNamespace
|
||||||
|
],
|
||||||
|
{silent}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('annotates resource', async () => {
|
it('annotates resource', async () => {
|
||||||
@@ -165,6 +194,27 @@ describe('Kubectl class', () => {
|
|||||||
],
|
],
|
||||||
{silent: false}
|
{silent: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// override ns
|
||||||
|
await kubectl.annotate(
|
||||||
|
resourceType,
|
||||||
|
resourceName,
|
||||||
|
annotation,
|
||||||
|
otherNamespace
|
||||||
|
)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(
|
||||||
|
kubectlPath,
|
||||||
|
[
|
||||||
|
'annotate',
|
||||||
|
resourceType,
|
||||||
|
resourceName,
|
||||||
|
annotation,
|
||||||
|
'--overwrite',
|
||||||
|
'--namespace',
|
||||||
|
otherNamespace
|
||||||
|
],
|
||||||
|
{silent: false}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('annotates files with single file', async () => {
|
it('annotates files with single file', async () => {
|
||||||
@@ -185,6 +235,22 @@ describe('Kubectl class', () => {
|
|||||||
],
|
],
|
||||||
{silent: false}
|
{silent: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// override ns
|
||||||
|
await kubectl.annotateFiles(file, annotation, otherNamespace)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(
|
||||||
|
kubectlPath,
|
||||||
|
[
|
||||||
|
'annotate',
|
||||||
|
'-f',
|
||||||
|
file,
|
||||||
|
annotation,
|
||||||
|
'--overwrite',
|
||||||
|
'--namespace',
|
||||||
|
otherNamespace
|
||||||
|
],
|
||||||
|
{silent: false}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('annotates files with mulitple files', async () => {
|
it('annotates files with mulitple files', async () => {
|
||||||
@@ -205,6 +271,22 @@ describe('Kubectl class', () => {
|
|||||||
],
|
],
|
||||||
{silent: false}
|
{silent: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// override ns
|
||||||
|
await kubectl.annotateFiles(files, annotation, otherNamespace)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(
|
||||||
|
kubectlPath,
|
||||||
|
[
|
||||||
|
'annotate',
|
||||||
|
'-f',
|
||||||
|
files.join(','),
|
||||||
|
annotation,
|
||||||
|
'--overwrite',
|
||||||
|
'--namespace',
|
||||||
|
otherNamespace
|
||||||
|
],
|
||||||
|
{silent: false}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('labels files with single file', async () => {
|
it('labels files with single file', async () => {
|
||||||
@@ -225,6 +307,21 @@ describe('Kubectl class', () => {
|
|||||||
],
|
],
|
||||||
{silent: false}
|
{silent: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await kubectl.labelFiles(file, labels, otherNamespace)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(
|
||||||
|
kubectlPath,
|
||||||
|
[
|
||||||
|
'label',
|
||||||
|
'-f',
|
||||||
|
file,
|
||||||
|
...labels,
|
||||||
|
'--overwrite',
|
||||||
|
'--namespace',
|
||||||
|
otherNamespace
|
||||||
|
],
|
||||||
|
{silent: false}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('labels files with multiple files', async () => {
|
it('labels files with multiple files', async () => {
|
||||||
@@ -245,6 +342,21 @@ describe('Kubectl class', () => {
|
|||||||
],
|
],
|
||||||
{silent: false}
|
{silent: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await kubectl.labelFiles(files, labels, otherNamespace)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(
|
||||||
|
kubectlPath,
|
||||||
|
[
|
||||||
|
'label',
|
||||||
|
'-f',
|
||||||
|
files.join(','),
|
||||||
|
...labels,
|
||||||
|
'--overwrite',
|
||||||
|
'--namespace',
|
||||||
|
otherNamespace
|
||||||
|
],
|
||||||
|
{silent: false}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('gets all pods', async () => {
|
it('gets all pods', async () => {
|
||||||
@@ -273,6 +385,20 @@ describe('Kubectl class', () => {
|
|||||||
],
|
],
|
||||||
{silent: false}
|
{silent: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// override ns
|
||||||
|
await kubectl.checkRolloutStatus(resourceType, name, otherNamespace)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(
|
||||||
|
kubectlPath,
|
||||||
|
[
|
||||||
|
'rollout',
|
||||||
|
'status',
|
||||||
|
`${resourceType}/${name}`,
|
||||||
|
'--namespace',
|
||||||
|
otherNamespace
|
||||||
|
],
|
||||||
|
{silent: false}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('gets resource', async () => {
|
it('gets resource', async () => {
|
||||||
@@ -291,6 +417,22 @@ describe('Kubectl class', () => {
|
|||||||
],
|
],
|
||||||
{silent: false}
|
{silent: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// override ns
|
||||||
|
const silent = true
|
||||||
|
await kubectl.getResource(resourceType, name, silent, otherNamespace)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(
|
||||||
|
kubectlPath,
|
||||||
|
[
|
||||||
|
'get',
|
||||||
|
`${resourceType}/${name}`,
|
||||||
|
'-o',
|
||||||
|
'json',
|
||||||
|
'--namespace',
|
||||||
|
otherNamespace
|
||||||
|
],
|
||||||
|
{silent}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('executes a command', async () => {
|
it('executes a command', async () => {
|
||||||
@@ -321,6 +463,14 @@ describe('Kubectl class', () => {
|
|||||||
['delete', arg, '--namespace', testNamespace],
|
['delete', arg, '--namespace', testNamespace],
|
||||||
{silent: false}
|
{silent: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// override ns
|
||||||
|
await kubectl.delete(arg, otherNamespace)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(
|
||||||
|
kubectlPath,
|
||||||
|
['delete', arg, '--namespace', otherNamespace],
|
||||||
|
{silent: false}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('deletes with multiple arguments', async () => {
|
it('deletes with multiple arguments', async () => {
|
||||||
@@ -331,6 +481,14 @@ describe('Kubectl class', () => {
|
|||||||
['delete', ...args, '--namespace', testNamespace],
|
['delete', ...args, '--namespace', testNamespace],
|
||||||
{silent: false}
|
{silent: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// override ns
|
||||||
|
await kubectl.delete(args, otherNamespace)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(
|
||||||
|
kubectlPath,
|
||||||
|
['delete', ...args, '--namespace', otherNamespace],
|
||||||
|
{silent: false}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -369,5 +527,11 @@ describe('Kubectl class', () => {
|
|||||||
[command, '--insecure-skip-tls-verify', '--namespace', testNamespace],
|
[command, '--insecure-skip-tls-verify', '--namespace', testNamespace],
|
||||||
{silent: false}
|
{silent: false}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const kubectlNoFlags = new Kubectl(kubectlPath)
|
||||||
|
kubectlNoFlags.executeCommand(command)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(kubectlPath, [command], {
|
||||||
|
silent: false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+60
-34
@@ -3,11 +3,11 @@ 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'
|
||||||
import {exec} from 'child_process'
|
|
||||||
|
|
||||||
export interface Resource {
|
export interface Resource {
|
||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
|
namespace?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Kubectl {
|
export class Kubectl {
|
||||||
@@ -20,7 +20,7 @@ export class Kubectl {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
kubectlPath: string,
|
kubectlPath: string,
|
||||||
namespace: string = 'default',
|
namespace: string = '',
|
||||||
ignoreSSLErrors: boolean = false,
|
ignoreSSLErrors: boolean = false,
|
||||||
resourceGroup: string = '',
|
resourceGroup: string = '',
|
||||||
name: string = ''
|
name: string = ''
|
||||||
@@ -47,7 +47,7 @@ export class Kubectl {
|
|||||||
]
|
]
|
||||||
if (force) applyArgs.push('--force')
|
if (force) applyArgs.push('--force')
|
||||||
|
|
||||||
return await this.execute(applyArgs)
|
return await this.execute(applyArgs.concat(this.getFlags()))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
core.debug('Kubectl apply failed:' + err)
|
core.debug('Kubectl apply failed:' + err)
|
||||||
}
|
}
|
||||||
@@ -56,27 +56,43 @@ export class Kubectl {
|
|||||||
public async describe(
|
public async describe(
|
||||||
resourceType: string,
|
resourceType: string,
|
||||||
resourceName: string,
|
resourceName: string,
|
||||||
silent: boolean = false
|
silent: boolean = false,
|
||||||
|
namespace?: string
|
||||||
): Promise<ExecOutput> {
|
): Promise<ExecOutput> {
|
||||||
return await this.execute(
|
return await this.execute(
|
||||||
['describe', resourceType, resourceName],
|
['describe', resourceType, resourceName].concat(
|
||||||
|
this.getFlags(namespace)
|
||||||
|
),
|
||||||
silent
|
silent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getNewReplicaSet(deployment: string) {
|
public async getNewReplicaSet(deployment: string, namespace?: string) {
|
||||||
const result = await this.describe('deployment', deployment, true)
|
const result = await this.describe(
|
||||||
|
'deployment',
|
||||||
|
deployment,
|
||||||
|
true,
|
||||||
|
namespace
|
||||||
|
)
|
||||||
|
|
||||||
let newReplicaSet = ''
|
let newReplicaSet = ''
|
||||||
if (result?.stdout) {
|
if (result?.stdout) {
|
||||||
const stdout = result.stdout.split('\n')
|
const stdout = result.stdout.split('\n')
|
||||||
|
core.debug('stdout from getNewReplicaSet is ' + JSON.stringify(stdout))
|
||||||
stdout.forEach((line: string) => {
|
stdout.forEach((line: string) => {
|
||||||
const newreplicaset = 'newreplicaset'
|
const newreplicaset = 'newreplicaset'
|
||||||
if (line && line.toLowerCase().indexOf(newreplicaset) > -1)
|
if (line && line.toLowerCase().indexOf(newreplicaset) > -1) {
|
||||||
|
core.debug(
|
||||||
|
`found string of interest for replicaset, line is ${line}`
|
||||||
|
)
|
||||||
|
core.debug(
|
||||||
|
`substring is ${line.substring(newreplicaset.length).trim()}`
|
||||||
|
)
|
||||||
newReplicaSet = line
|
newReplicaSet = line
|
||||||
.substring(newreplicaset.length)
|
.substring(newreplicaset.length)
|
||||||
.trim()
|
.trim()
|
||||||
.split(' ')[0]
|
.split(' ')[0]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +102,8 @@ export class Kubectl {
|
|||||||
public async annotate(
|
public async annotate(
|
||||||
resourceType: string,
|
resourceType: string,
|
||||||
resourceName: string,
|
resourceName: string,
|
||||||
annotation: string
|
annotation: string,
|
||||||
|
namespace?: string
|
||||||
): Promise<ExecOutput> {
|
): Promise<ExecOutput> {
|
||||||
const args = [
|
const args = [
|
||||||
'annotate',
|
'annotate',
|
||||||
@@ -94,13 +111,14 @@ export class Kubectl {
|
|||||||
resourceName,
|
resourceName,
|
||||||
annotation,
|
annotation,
|
||||||
'--overwrite'
|
'--overwrite'
|
||||||
]
|
].concat(this.getFlags(namespace))
|
||||||
return await this.execute(args)
|
return await this.execute(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async annotateFiles(
|
public async annotateFiles(
|
||||||
files: string | string[],
|
files: string | string[],
|
||||||
annotation: string
|
annotation: string,
|
||||||
|
namespace?: string
|
||||||
): Promise<ExecOutput> {
|
): Promise<ExecOutput> {
|
||||||
const filesToAnnotate = createInlineArray(files)
|
const filesToAnnotate = createInlineArray(files)
|
||||||
core.debug(`annotating ${filesToAnnotate} with annotation ${annotation}`)
|
core.debug(`annotating ${filesToAnnotate} with annotation ${annotation}`)
|
||||||
@@ -110,16 +128,14 @@ export class Kubectl {
|
|||||||
filesToAnnotate,
|
filesToAnnotate,
|
||||||
annotation,
|
annotation,
|
||||||
'--overwrite'
|
'--overwrite'
|
||||||
]
|
].concat(this.getFlags(namespace))
|
||||||
core.debug(
|
|
||||||
`sending args from annotate to execute: ${JSON.stringify(args)}`
|
|
||||||
)
|
|
||||||
return await this.execute(args)
|
return await this.execute(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async labelFiles(
|
public async labelFiles(
|
||||||
files: string | string[],
|
files: string | string[],
|
||||||
labels: string[]
|
labels: string[],
|
||||||
|
namespace?: string
|
||||||
): Promise<ExecOutput> {
|
): Promise<ExecOutput> {
|
||||||
const args = [
|
const args = [
|
||||||
'label',
|
'label',
|
||||||
@@ -127,51 +143,59 @@ export class Kubectl {
|
|||||||
createInlineArray(files),
|
createInlineArray(files),
|
||||||
...labels,
|
...labels,
|
||||||
'--overwrite'
|
'--overwrite'
|
||||||
]
|
].concat(this.getFlags(namespace))
|
||||||
return await this.execute(args)
|
return await this.execute(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllPods(): Promise<ExecOutput> {
|
public async getAllPods(): Promise<ExecOutput> {
|
||||||
return await this.execute(['get', 'pods', '-o', 'json'], true)
|
return await this.execute(
|
||||||
|
['get', 'pods', '-o', 'json'].concat(this.getFlags()),
|
||||||
|
true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async checkRolloutStatus(
|
public async checkRolloutStatus(
|
||||||
resourceType: string,
|
resourceType: string,
|
||||||
name: string
|
name: string,
|
||||||
|
namespace?: string
|
||||||
): Promise<ExecOutput> {
|
): Promise<ExecOutput> {
|
||||||
return await this.execute([
|
return await this.execute(
|
||||||
'rollout',
|
['rollout', 'status', `${resourceType}/${name}`].concat(
|
||||||
'status',
|
this.getFlags(namespace)
|
||||||
`${resourceType}/${name}`
|
)
|
||||||
])
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getResource(
|
public async getResource(
|
||||||
resourceType: string,
|
resourceType: string,
|
||||||
name: string,
|
name: string,
|
||||||
silentFailure: boolean = false
|
silentFailure: boolean = false,
|
||||||
|
namespace?: string
|
||||||
): Promise<ExecOutput> {
|
): Promise<ExecOutput> {
|
||||||
core.debug(
|
core.debug(
|
||||||
'fetching resource of type ' + resourceType + ' and name ' + name
|
'fetching resource of type ' + resourceType + ' and name ' + name
|
||||||
)
|
)
|
||||||
return await this.execute(
|
return await this.execute(
|
||||||
['get', `${resourceType}/${name}`, '-o', 'json'],
|
['get', `${resourceType}/${name}`, '-o', 'json'].concat(
|
||||||
|
this.getFlags(namespace)
|
||||||
|
),
|
||||||
silentFailure
|
silentFailure
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public executeCommand(command: string, args?: string) {
|
public executeCommand(command: string, args?: string) {
|
||||||
if (!command) throw new Error('Command must be defined')
|
if (!command) throw new Error('Command must be defined')
|
||||||
return args ? this.execute([command, args]) : this.execute([command])
|
const a = args ? [args] : []
|
||||||
|
return this.execute([command, ...a.concat(this.getFlags())])
|
||||||
}
|
}
|
||||||
|
|
||||||
public delete(args: string | string[]) {
|
public delete(args: string | string[], namespace?: string) {
|
||||||
if (typeof args === 'string') return this.execute(['delete', args])
|
if (typeof args === 'string')
|
||||||
return this.execute(['delete', ...args])
|
return this.execute(['delete', args].concat(this.getFlags(namespace)))
|
||||||
|
return this.execute(['delete', ...args.concat(this.getFlags(namespace))])
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async execute(args: string[], silent: boolean = false) {
|
protected async execute(args: string[], silent: boolean = false) {
|
||||||
args = args.concat(this.getExecuteFlags())
|
|
||||||
core.debug(`Kubectl run with command: ${this.kubectlPath} ${args}`)
|
core.debug(`Kubectl run with command: ${this.kubectlPath} ${args}`)
|
||||||
|
|
||||||
return await getExecOutput(this.kubectlPath, args, {
|
return await getExecOutput(this.kubectlPath, args, {
|
||||||
@@ -179,13 +203,15 @@ export class Kubectl {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getExecuteFlags(): 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')
|
||||||
}
|
}
|
||||||
if (this.namespace) {
|
|
||||||
flags.push('--namespace', this.namespace)
|
const ns = namespaceOverride || this.namespace
|
||||||
|
if (ns) {
|
||||||
|
flags.push('--namespace', ns)
|
||||||
}
|
}
|
||||||
|
|
||||||
return flags
|
return flags
|
||||||
|
|||||||
@@ -1,12 +1,61 @@
|
|||||||
import {PrivateKubectl} from './privatekubectl'
|
import * as fileUtils from '../utilities/fileUtils'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import {
|
||||||
|
PrivateKubectl,
|
||||||
|
extractFileNames,
|
||||||
|
replaceFileNamesWithShallowNamesRelativeToTemp
|
||||||
|
} from './privatekubectl'
|
||||||
|
import * as exec from '@actions/exec'
|
||||||
|
|
||||||
describe('Private kubectl', () => {
|
describe('Private kubectl', () => {
|
||||||
const testString = `kubectl annotate -f test.yml,test2.yml,test3.yml -f test4.yml --filename test5.yml actions.github.com/k8s-deploy={"run":"3498366832","repository":"jaiveerk/k8s-deploy","workflow":"Minikube Integration Tests - private cluster","workflowFileName":"run-integration-tests-private.yml","jobName":"run-integration-test","createdBy":"jaiveerk","runUri":"https://github.com/jaiveerk/k8s-deploy/actions/runs/3498366832","commit":"c63b323186ea1320a31290de6dcc094c06385e75","lastSuccessRunCommit":"NA","branch":"refs/heads/main","deployTimestamp":1668787848577,"dockerfilePaths":{"nginx:1.14.2":""},"manifestsPaths":["https://github.com/jaiveerk/k8s-deploy/blob/c63b323186ea1320a31290de6dcc094c06385e75/test/integration/manifests/test.yml"],"helmChartPaths":[],"provider":"GitHub"} --overwrite --namespace test-3498366832`
|
const testString = `kubectl annotate -f /tmp/testdir/test.yml,/tmp/test2.yml,/tmp/testdir/subdir/test3.yml -f /tmp/test4.yml --filename /tmp/test5.yml actions.github.com/k8s-deploy={"run":"3498366832","repository":"jaiveerk/k8s-deploy","workflow":"Minikube Integration Tests - private cluster","workflowFileName":"run-integration-tests-private.yml","jobName":"run-integration-test","createdBy":"jaiveerk","runUri":"https://github.com/jaiveerk/k8s-deploy/actions/runs/3498366832","commit":"c63b323186ea1320a31290de6dcc094c06385e75","lastSuccessRunCommit":"NA","branch":"refs/heads/main","deployTimestamp":1668787848577,"dockerfilePaths":{"nginx:1.14.2":""},"manifestsPaths":["https://github.com/jaiveerk/k8s-deploy/blob/c63b323186ea1320a31290de6dcc094c06385e75/test/integration/test.yml"],"helmChartPaths":[],"provider":"GitHub"} --overwrite --namespace test-3498366832`
|
||||||
const mockKube = new PrivateKubectl('')
|
const mockKube = new PrivateKubectl(
|
||||||
|
'kubectlPath',
|
||||||
|
'namespace',
|
||||||
|
true,
|
||||||
|
'resourceGroup',
|
||||||
|
'resourceName'
|
||||||
|
)
|
||||||
|
|
||||||
|
const spy = jest
|
||||||
|
.spyOn(fileUtils, 'getTempDirectory')
|
||||||
|
.mockImplementation(() => {
|
||||||
|
return '/tmp'
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
|
||||||
|
jest.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
|
||||||
|
return 'test contents'
|
||||||
|
})
|
||||||
|
|
||||||
it('should extract filenames correctly', () => {
|
it('should extract filenames correctly', () => {
|
||||||
expect(mockKube.extractFilesnames(testString)).toEqual(
|
expect(extractFileNames(testString)).toEqual([
|
||||||
'test.yml test2.yml test3.yml test4.yml test5.yml'
|
'/tmp/testdir/test.yml',
|
||||||
|
'/tmp/test2.yml',
|
||||||
|
'/tmp/testdir/subdir/test3.yml',
|
||||||
|
'/tmp/test4.yml',
|
||||||
|
'/tmp/test5.yml'
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should replace filenames with shallow names for relative locations in tmp correctly', () => {
|
||||||
|
expect(
|
||||||
|
replaceFileNamesWithShallowNamesRelativeToTemp(testString)
|
||||||
|
).toEqual(
|
||||||
|
`kubectl annotate -f testdir-test.yml,test2.yml,testdir-subdir-test3.yml -f test4.yml --filename test5.yml actions.github.com/k8s-deploy={"run":"3498366832","repository":"jaiveerk/k8s-deploy","workflow":"Minikube Integration Tests - private cluster","workflowFileName":"run-integration-tests-private.yml","jobName":"run-integration-test","createdBy":"jaiveerk","runUri":"https://github.com/jaiveerk/k8s-deploy/actions/runs/3498366832","commit":"c63b323186ea1320a31290de6dcc094c06385e75","lastSuccessRunCommit":"NA","branch":"refs/heads/main","deployTimestamp":1668787848577,"dockerfilePaths":{"nginx:1.14.2":""},"manifestsPaths":["https://github.com/jaiveerk/k8s-deploy/blob/c63b323186ea1320a31290de6dcc094c06385e75/test/integration/test.yml"],"helmChartPaths":[],"provider":"GitHub"} --overwrite --namespace test-3498366832`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Should throw well defined Error on error from Azure', async () => {
|
||||||
|
const errorMsg = 'An error message'
|
||||||
|
jest.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
|
||||||
|
return {exitCode: 1, stdout: '', stderr: errorMsg}
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(mockKube.executeCommand('az', 'test')).rejects.toThrow(
|
||||||
|
Error(
|
||||||
|
`Call to private cluster failed. Command: 'kubectl az test --insecure-skip-tls-verify --namespace namespace', errormessage: ${errorMsg}`
|
||||||
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+96
-105
@@ -1,15 +1,13 @@
|
|||||||
import {Kubectl} from './kubectl'
|
import {Kubectl} from './kubectl'
|
||||||
import * as 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 * as os from 'os'
|
import fs from 'node:fs'
|
||||||
import * as fs from 'fs'
|
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
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) {
|
||||||
args = args.concat(this.getExecuteFlags())
|
|
||||||
|
|
||||||
args.unshift('kubectl')
|
args.unshift('kubectl')
|
||||||
let kubectlCmd = args.join(' ')
|
let kubectlCmd = args.join(' ')
|
||||||
let addFileFlag = false
|
let addFileFlag = false
|
||||||
@@ -20,8 +18,7 @@ export class PrivateKubectl extends Kubectl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.containsFilenames(kubectlCmd)) {
|
if (this.containsFilenames(kubectlCmd)) {
|
||||||
// For private clusters, files will referenced solely by their basename
|
kubectlCmd = replaceFileNamesWithShallowNamesRelativeToTemp(kubectlCmd)
|
||||||
kubectlCmd = this.replaceFilnamesWithBasenames(kubectlCmd)
|
|
||||||
addFileFlag = true
|
addFileFlag = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,22 +42,9 @@ export class PrivateKubectl extends Kubectl {
|
|||||||
]
|
]
|
||||||
|
|
||||||
if (addFileFlag) {
|
if (addFileFlag) {
|
||||||
const filenames = this.extractFilesnames(kubectlCmd).split(' ')
|
const tempDirectory = getTempDirectory()
|
||||||
|
eo.cwd = path.join(tempDirectory, 'manifests')
|
||||||
const tempDirectory =
|
|
||||||
process.env['runner.tempDirectory'] || os.tmpdir() + '/manifests'
|
|
||||||
eo.cwd = tempDirectory
|
|
||||||
privateClusterArgs.push(...['--file', '.'])
|
privateClusterArgs.push(...['--file', '.'])
|
||||||
|
|
||||||
let filenamesArr = filenames[0].split(',')
|
|
||||||
for (let index = 0; index < filenamesArr.length; index++) {
|
|
||||||
const file = filenamesArr[index]
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
this.moveFileToTempManifestDir(file)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
core.debug(
|
core.debug(
|
||||||
@@ -75,11 +59,18 @@ export class PrivateKubectl extends Kubectl {
|
|||||||
runOutput
|
runOutput
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (runOutput.exitCode !== 0) {
|
||||||
|
throw Error(
|
||||||
|
`Call to private cluster failed. Command: '${kubectlCmd}', errormessage: ${runOutput.stderr}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const runObj: {logs: string; exitCode: number} = JSON.parse(
|
const runObj: {logs: string; exitCode: number} = JSON.parse(
|
||||||
runOutput.stdout
|
runOutput.stdout
|
||||||
)
|
)
|
||||||
if (!silent) core.info(runObj.logs)
|
if (!silent) core.info(runObj.logs)
|
||||||
if (runOutput.exitCode !== 0 && runObj.exitCode !== 0) {
|
if (runObj.exitCode !== 0) {
|
||||||
throw Error(`failed private cluster Kubectl command: ${kubectlCmd}`)
|
throw Error(`failed private cluster Kubectl command: ${kubectlCmd}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,89 +81,89 @@ export class PrivateKubectl extends Kubectl {
|
|||||||
} as ExecOutput
|
} as ExecOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
private replaceFilnamesWithBasenames(kubectlCmd: string) {
|
|
||||||
let exFilenames = this.extractFilesnames(kubectlCmd)
|
|
||||||
let filenames = exFilenames.split(' ')
|
|
||||||
let filenamesArr = filenames[0].split(',')
|
|
||||||
|
|
||||||
for (let index = 0; index < filenamesArr.length; index++) {
|
|
||||||
filenamesArr[index] = path.basename(filenamesArr[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
let baseFilenames = filenamesArr.join()
|
|
||||||
|
|
||||||
let result = kubectlCmd.replace(exFilenames, baseFilenames)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
public extractFilesnames(strToParse: string) {
|
|
||||||
const fileNames: string[] = []
|
|
||||||
const argv = minimist(strToParse.split(' '))
|
|
||||||
const fArg = 'f'
|
|
||||||
const filenameArg = 'filename'
|
|
||||||
|
|
||||||
fileNames.push(...this.extractFilesFromMinimist(argv, fArg))
|
|
||||||
fileNames.push(...this.extractFilesFromMinimist(argv, filenameArg))
|
|
||||||
|
|
||||||
return fileNames.join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractFilesFromMinimist(argv, arg: string): string[] {
|
|
||||||
if (!argv[arg]) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const toReturn: string[] = []
|
|
||||||
if (typeof argv[arg] === 'string') {
|
|
||||||
toReturn.push(...argv[arg].split(','))
|
|
||||||
} else {
|
|
||||||
for (const value of argv[arg] as string[]) {
|
|
||||||
toReturn.push(...value.split(','))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return toReturn
|
|
||||||
}
|
|
||||||
|
|
||||||
private containsFilenames(str: string) {
|
private containsFilenames(str: string) {
|
||||||
return str.includes('-f ') || str.includes('filename ')
|
return str.includes('-f ') || str.includes('filename ')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private createTempManifestsDirectory() {
|
|
||||||
const manifestsDir = '/tmp/manifests'
|
function createTempManifestsDirectory(): string {
|
||||||
if (!fs.existsSync('/tmp/manifests')) {
|
const manifestsDirPath = path.join(getTempDirectory(), 'manifests')
|
||||||
fs.mkdirSync('/tmp/manifests', {recursive: true})
|
if (!fs.existsSync(manifestsDirPath)) {
|
||||||
}
|
fs.mkdirSync(manifestsDirPath, {recursive: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
private moveFileToTempManifestDir(file: string) {
|
return manifestsDirPath
|
||||||
this.createTempManifestsDirectory()
|
}
|
||||||
if (!fs.existsSync('/tmp/' + file)) {
|
|
||||||
core.debug(
|
export function replaceFileNamesWithShallowNamesRelativeToTemp(
|
||||||
'/tmp/' +
|
kubectlCmd: string
|
||||||
file +
|
) {
|
||||||
' does not exist, and therefore cannot be moved to the manifest directory'
|
let filenames = extractFileNames(kubectlCmd)
|
||||||
)
|
core.debug(`filenames originally provided in kubectl command: ${filenames}`)
|
||||||
}
|
let relativeShallowNames = filenames.map((filename) => {
|
||||||
|
const relativeName = path.relative(getTempDirectory(), filename)
|
||||||
fs.copyFile('/tmp/' + file, '/tmp/manifests/' + file, function (err) {
|
|
||||||
if (err) {
|
const relativePathElements = relativeName.split(path.sep)
|
||||||
core.debug(
|
|
||||||
'Could not rename ' +
|
const shallowName = relativePathElements.join('-')
|
||||||
'/tmp/' +
|
|
||||||
file +
|
// make manifests dir in temp if it doesn't already exist
|
||||||
' to ' +
|
const manifestsTempDir = createTempManifestsDirectory()
|
||||||
'/tmp/manifests/' +
|
|
||||||
file +
|
const shallowPath = path.join(manifestsTempDir, shallowName)
|
||||||
' ERROR: ' +
|
core.debug(
|
||||||
err
|
`moving contents from ${filename} to shallow location at ${shallowPath}`
|
||||||
)
|
)
|
||||||
return
|
|
||||||
}
|
core.debug(`reading contents from ${filename}`)
|
||||||
core.debug(
|
const contents = fs.readFileSync(filename).toString()
|
||||||
"Successfully moved file '" +
|
|
||||||
file +
|
core.debug(`writing contents to new path ${shallowPath}`)
|
||||||
"' from /tmp to /tmp/manifest directory"
|
fs.writeFileSync(shallowPath, contents)
|
||||||
)
|
|
||||||
})
|
return shallowName
|
||||||
}
|
})
|
||||||
|
|
||||||
|
let result = kubectlCmd
|
||||||
|
if (filenames.length != relativeShallowNames.length) {
|
||||||
|
throw Error(
|
||||||
|
'replacing filenames with relative path from temp dir, ' +
|
||||||
|
filenames.length +
|
||||||
|
' filenames != ' +
|
||||||
|
relativeShallowNames.length +
|
||||||
|
'basenames'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for (let index = 0; index < filenames.length; index++) {
|
||||||
|
result = result.replace(filenames[index], relativeShallowNames[index])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractFileNames(strToParse: string) {
|
||||||
|
const fileNames: string[] = []
|
||||||
|
const argv = minimist(strToParse.split(' '))
|
||||||
|
const fArg = 'f'
|
||||||
|
const filenameArg = 'filename'
|
||||||
|
|
||||||
|
fileNames.push(...extractFilesFromMinimist(argv, fArg))
|
||||||
|
fileNames.push(...extractFilesFromMinimist(argv, filenameArg))
|
||||||
|
|
||||||
|
return fileNames
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractFilesFromMinimist(argv, arg: string): string[] {
|
||||||
|
if (!argv[arg]) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const toReturn: string[] = []
|
||||||
|
if (typeof argv[arg] === 'string') {
|
||||||
|
toReturn.push(...argv[arg].split(','))
|
||||||
|
} else {
|
||||||
|
for (const value of argv[arg] as string[]) {
|
||||||
|
toReturn.push(...value.split(','))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,11 @@ export async function getDeploymentConfig(): Promise<DeploymentConfig> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageNames = core.getInput('images').split('\n') || []
|
const imageNames =
|
||||||
|
core
|
||||||
|
.getInput('images')
|
||||||
|
.split('\n')
|
||||||
|
.filter((image) => image.length > 0) || []
|
||||||
const imageDockerfilePathMap: {[id: string]: string} = {}
|
const imageDockerfilePathMap: {[id: string]: string} = {}
|
||||||
|
|
||||||
const pullImages = !(core.getInput('pull-images').toLowerCase() === 'false')
|
const pullImages = !(core.getInput('pull-images').toLowerCase() === 'false')
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
import {
|
import * as fileUtils from './fileUtils'
|
||||||
getFilesFromDirectoriesAndURLs,
|
|
||||||
getTempDirectory,
|
|
||||||
urlFileKind,
|
|
||||||
writeYamlFromURLToFile
|
|
||||||
} from './fileUtils'
|
|
||||||
|
|
||||||
import * as yaml from 'js-yaml'
|
import * as yaml from 'js-yaml'
|
||||||
import * as fs from 'fs'
|
import fs from 'node:fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import {succeeded} from '../types/errorable'
|
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', () => {
|
||||||
test('correctly parses a yaml file from a URL', async () => {
|
test('correctly parses a yaml file from a URL', async () => {
|
||||||
const tempFile = await writeYamlFromURLToFile(sampleYamlUrl, 0)
|
const tempFile = await fileUtils.writeYamlFromURLToFile(sampleYamlUrl, 0)
|
||||||
const fileContents = fs.readFileSync(tempFile).toString()
|
const fileContents = fs.readFileSync(tempFile).toString()
|
||||||
const inputObjects = yaml.safeLoadAll(fileContents)
|
const inputObjects: K8sObject[] = yaml.loadAll(
|
||||||
|
fileContents
|
||||||
|
) as K8sObject[]
|
||||||
expect(inputObjects).toHaveLength(1)
|
expect(inputObjects).toHaveLength(1)
|
||||||
|
|
||||||
for (const obj of inputObjects) {
|
for (const obj of inputObjects) {
|
||||||
@@ -30,34 +27,34 @@ describe('File utils', () => {
|
|||||||
|
|
||||||
const testPath = path.join('test', 'unit', 'manifests')
|
const testPath = path.join('test', 'unit', 'manifests')
|
||||||
await expect(
|
await expect(
|
||||||
getFilesFromDirectoriesAndURLs([testPath, badUrl])
|
fileUtils.getFilesFromDirectoriesAndURLs([testPath, badUrl])
|
||||||
).rejects.toThrow()
|
).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('detects files in nested directories and ignores non-manifest files and empty dirs', async () => {
|
it('detects files in nested directories with the same name and ignores non-manifest files and empty dirs', async () => {
|
||||||
const testPath = path.join('test', 'unit', 'manifests')
|
const testPath = path.join('test', 'unit', 'manifests')
|
||||||
const testSearch: string[] = await getFilesFromDirectoriesAndURLs([
|
const testSearch: string[] =
|
||||||
testPath,
|
await fileUtils.getFilesFromDirectoriesAndURLs([
|
||||||
sampleYamlUrl
|
testPath,
|
||||||
])
|
sampleYamlUrl
|
||||||
|
])
|
||||||
|
|
||||||
const expectedManifests = [
|
const expectedManifests = [
|
||||||
'test/unit/manifests/manifest_test_dir/another_layer/deep-ingress.yaml',
|
'test/unit/manifests/manifest_test_dir/another_layer/test-ingress.yaml',
|
||||||
'test/unit/manifests/manifest_test_dir/another_layer/deep-service.yaml',
|
'test/unit/manifests/manifest_test_dir/another_layer/nested-test-service.yaml',
|
||||||
'test/unit/manifests/manifest_test_dir/nested-test-service.yaml',
|
'test/unit/manifests/manifest_test_dir/nested-test-service.yaml',
|
||||||
'test/unit/manifests/test-ingress.yml',
|
'test/unit/manifests/test-ingress.yml',
|
||||||
'test/unit/manifests/test-ingress-new.yml',
|
'test/unit/manifests/test-ingress-new.yml',
|
||||||
'test/unit/manifests/test-service.yml'
|
'test/unit/manifests/test-service.yml'
|
||||||
]
|
]
|
||||||
|
|
||||||
// is there a more efficient way to test equality w random order?
|
|
||||||
expect(testSearch).toHaveLength(8)
|
expect(testSearch).toHaveLength(8)
|
||||||
expectedManifests.forEach((fileName) => {
|
expectedManifests.forEach((fileName) => {
|
||||||
if (fileName.startsWith('test/unit')) {
|
if (fileName.startsWith('test/unit')) {
|
||||||
expect(testSearch).toContain(fileName)
|
expect(testSearch).toContain(fileName)
|
||||||
} else {
|
} else {
|
||||||
expect(fileName.includes(urlFileKind)).toBe(true)
|
expect(fileName.includes(fileUtils.urlFileKind)).toBe(true)
|
||||||
expect(fileName.startsWith(getTempDirectory()))
|
expect(fileName.startsWith(fileUtils.getTempDirectory()))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -72,7 +69,7 @@ describe('File utils', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
getFilesFromDirectoriesAndURLs([badPath, goodPath])
|
fileUtils.getFilesFromDirectoriesAndURLs([badPath, goodPath])
|
||||||
).rejects.toThrowError()
|
).rejects.toThrowError()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -92,7 +89,7 @@ describe('File utils', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await getFilesFromDirectoriesAndURLs([
|
await fileUtils.getFilesFromDirectoriesAndURLs([
|
||||||
outerPath,
|
outerPath,
|
||||||
fileAtOuter,
|
fileAtOuter,
|
||||||
innerPath
|
innerPath
|
||||||
@@ -102,6 +99,24 @@ describe('File utils', () => {
|
|||||||
|
|
||||||
it('throws an error for an invalid URL', async () => {
|
it('throws an error for an invalid URL', async () => {
|
||||||
const badUrl = 'https://www.github.com'
|
const badUrl = 'https://www.github.com'
|
||||||
await expect(writeYamlFromURLToFile(badUrl, 0)).rejects.toBeTruthy()
|
await expect(
|
||||||
|
fileUtils.writeYamlFromURLToFile(badUrl, 0)
|
||||||
|
).rejects.toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('moving files to temp', () => {
|
||||||
|
it('correctly moves the contents of a file to the temporary directory', () => {
|
||||||
|
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
|
||||||
|
jest.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
|
||||||
|
return 'test contents'
|
||||||
|
})
|
||||||
|
const originalFilePath = path.join('path', 'in', 'repo')
|
||||||
|
|
||||||
|
const output = fileUtils.moveFileToTmpDir(originalFilePath)
|
||||||
|
|
||||||
|
expect(output).toEqual(
|
||||||
|
path.join(fileUtils.getTempDirectory(), '/path/in/repo')
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as fs from 'fs'
|
import fs from 'node:fs'
|
||||||
import * as https from 'https'
|
import * as https from 'https'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
@@ -23,7 +23,7 @@ export function writeObjectsToFile(inputObjects: any[]): string[] {
|
|||||||
const inputObjectString = JSON.stringify(inputObject)
|
const inputObjectString = JSON.stringify(inputObject)
|
||||||
|
|
||||||
if (inputObject?.metadata?.name) {
|
if (inputObject?.metadata?.name) {
|
||||||
const fileName = getManifestFileName(
|
const fileName = getNewTempManifestFileName(
|
||||||
inputObject.kind,
|
inputObject.kind,
|
||||||
inputObject.metadata.name
|
inputObject.metadata.name
|
||||||
)
|
)
|
||||||
@@ -52,7 +52,7 @@ export function writeManifestToFile(
|
|||||||
): string {
|
): string {
|
||||||
if (inputObjectString) {
|
if (inputObjectString) {
|
||||||
try {
|
try {
|
||||||
const fileName = getManifestFileName(kind, name)
|
const fileName = getNewTempManifestFileName(kind, name)
|
||||||
fs.writeFileSync(path.join(fileName), inputObjectString)
|
fs.writeFileSync(path.join(fileName), inputObjectString)
|
||||||
return fileName
|
return fileName
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@@ -63,7 +63,27 @@ export function writeManifestToFile(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getManifestFileName(kind: string, name: string) {
|
export function moveFileToTmpDir(originalFilepath: string) {
|
||||||
|
const tempDirectory = getTempDirectory()
|
||||||
|
const newPath = path.join(tempDirectory, originalFilepath)
|
||||||
|
|
||||||
|
core.debug(`reading original contents from path: ${originalFilepath}`)
|
||||||
|
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}`)
|
||||||
|
fs.writeFileSync(path.join(newPath), contents)
|
||||||
|
|
||||||
|
core.debug(`moved contents from ${originalFilepath} to ${newPath}`)
|
||||||
|
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNewTempManifestFileName(kind: string, name: string) {
|
||||||
const filePath = `${kind}_${name}_${getCurrentTime().toString()}`
|
const filePath = `${kind}_${name}_${getCurrentTime().toString()}`
|
||||||
const tempDirectory = getTempDirectory()
|
const tempDirectory = getTempDirectory()
|
||||||
return path.join(tempDirectory, path.basename(filePath))
|
return path.join(tempDirectory, path.basename(filePath))
|
||||||
@@ -130,7 +150,7 @@ export async function writeYamlFromURLToFile(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetPath = getManifestFileName(
|
const targetPath = getNewTempManifestFileName(
|
||||||
urlFileKind,
|
urlFileKind,
|
||||||
fileNumber.toString()
|
fileNumber.toString()
|
||||||
)
|
)
|
||||||
@@ -163,7 +183,7 @@ function verifyYaml(filepath: string, url: string): Errorable<K8sObject[]> {
|
|||||||
const fileContents = fs.readFileSync(filepath).toString()
|
const fileContents = fs.readFileSync(filepath).toString()
|
||||||
let inputObjects
|
let inputObjects
|
||||||
try {
|
try {
|
||||||
inputObjects = yaml.safeLoadAll(fileContents)
|
inputObjects = yaml.loadAll(fileContents)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {
|
return {
|
||||||
succeeded: false,
|
succeeded: false,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import * as core from '@actions/core'
|
|||||||
import {ExecOutput} from '@actions/exec'
|
import {ExecOutput} from '@actions/exec'
|
||||||
import {Kubectl} from '../types/kubectl'
|
import {Kubectl} from '../types/kubectl'
|
||||||
|
|
||||||
|
const NAMESPACE = 'namespace'
|
||||||
|
|
||||||
export function checkForErrors(
|
export function checkForErrors(
|
||||||
execResults: ExecOutput[],
|
execResults: ExecOutput[],
|
||||||
warnIfError?: boolean
|
warnIfError?: boolean
|
||||||
@@ -30,7 +32,12 @@ export async function getLastSuccessfulRunSha(
|
|||||||
annotationKey: string
|
annotationKey: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const result = await kubectl.getResource('namespace', namespaceName)
|
const result = await kubectl.getResource(
|
||||||
|
NAMESPACE,
|
||||||
|
namespaceName,
|
||||||
|
false,
|
||||||
|
namespaceName
|
||||||
|
)
|
||||||
if (result?.stderr) {
|
if (result?.stderr) {
|
||||||
core.warning(result.stderr)
|
core.warning(result.stderr)
|
||||||
return process.env.GITHUB_SHA
|
return process.env.GITHUB_SHA
|
||||||
@@ -53,15 +60,23 @@ export async function annotateChildPods(
|
|||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
resourceType: string,
|
resourceType: string,
|
||||||
resourceName: string,
|
resourceName: string,
|
||||||
annotationKeyValStr: string,
|
namespace: string | undefined,
|
||||||
allPods
|
annotationKeyValStr: string
|
||||||
): Promise<ExecOutput[]> {
|
): Promise<ExecOutput[]> {
|
||||||
let owner = resourceName
|
let owner = resourceName
|
||||||
if (resourceType.toLowerCase().indexOf('deployment') > -1) {
|
if (resourceType.toLowerCase().indexOf('deployment') > -1) {
|
||||||
owner = await kubectl.getNewReplicaSet(resourceName)
|
owner = await kubectl.getNewReplicaSet(resourceName, namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandExecutionResults = []
|
const commandExecutionResults = []
|
||||||
|
|
||||||
|
let allPods
|
||||||
|
try {
|
||||||
|
allPods = JSON.parse((await kubectl.getAllPods()).stdout)
|
||||||
|
} catch (e) {
|
||||||
|
core.debug(`Unable to parse pods: ${e}`)
|
||||||
|
}
|
||||||
|
|
||||||
if (allPods?.items && allPods.items?.length > 0) {
|
if (allPods?.items && allPods.items?.length > 0) {
|
||||||
allPods.items.forEach((pod) => {
|
allPods.items.forEach((pod) => {
|
||||||
const owners = pod?.metadata?.ownerReferences
|
const owners = pod?.metadata?.ownerReferences
|
||||||
@@ -72,7 +87,8 @@ export async function annotateChildPods(
|
|||||||
kubectl.annotate(
|
kubectl.annotate(
|
||||||
'pod',
|
'pod',
|
||||||
pod.metadata.name,
|
pod.metadata.name,
|
||||||
annotationKeyValStr
|
annotationKeyValStr,
|
||||||
|
namespace
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import * as manifestStabilityUtils from './manifestStabilityUtils'
|
||||||
|
import {Kubectl} from '../types/kubectl'
|
||||||
|
import {ResourceTypeFleet, ResourceTypeManagedCluster} from '../actions/deploy'
|
||||||
|
import {ExecOutput} from '@actions/exec'
|
||||||
|
import {exitCode, stdout} from 'process'
|
||||||
|
|
||||||
|
describe('manifestStabilityUtils', () => {
|
||||||
|
const kc = new Kubectl('')
|
||||||
|
const resources = [
|
||||||
|
{
|
||||||
|
type: 'deployment',
|
||||||
|
name: 'test',
|
||||||
|
namespace: 'default'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
it('should return immediately if the resource type is fleet', async () => {
|
||||||
|
const spy = jest.spyOn(manifestStabilityUtils, 'checkManifestStability')
|
||||||
|
const checkRolloutStatusSpy = jest.spyOn(kc, 'checkRolloutStatus')
|
||||||
|
await manifestStabilityUtils.checkManifestStability(
|
||||||
|
kc,
|
||||||
|
resources,
|
||||||
|
ResourceTypeFleet
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(checkRolloutStatusSpy).not.toHaveBeenCalled()
|
||||||
|
expect(spy).toHaveReturned()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should run fully if the resource type is managedCluster', async () => {
|
||||||
|
const spy = jest.spyOn(manifestStabilityUtils, 'checkManifestStability')
|
||||||
|
const checkRolloutStatusSpy = jest
|
||||||
|
.spyOn(kc, 'checkRolloutStatus')
|
||||||
|
.mockImplementation(() => {
|
||||||
|
return new Promise<ExecOutput>((resolve, reject) => {
|
||||||
|
resolve({
|
||||||
|
exitCode: 0,
|
||||||
|
stderr: '',
|
||||||
|
stdout: ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await manifestStabilityUtils.checkManifestStability(
|
||||||
|
kc,
|
||||||
|
resources,
|
||||||
|
ResourceTypeManagedCluster
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(checkRolloutStatusSpy).toHaveBeenCalled()
|
||||||
|
expect(spy).toHaveReturned()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,11 +3,21 @@ import * as KubernetesConstants from '../types/kubernetesTypes'
|
|||||||
import {Kubectl, Resource} from '../types/kubectl'
|
import {Kubectl, Resource} from '../types/kubectl'
|
||||||
import {checkForErrors} from './kubectlUtils'
|
import {checkForErrors} from './kubectlUtils'
|
||||||
import {sleep} from './timeUtils'
|
import {sleep} from './timeUtils'
|
||||||
|
import {ClusterType, ResourceTypeFleet} from '../actions/deploy'
|
||||||
|
|
||||||
|
const IS_SILENT = false
|
||||||
|
const POD = 'pod'
|
||||||
|
|
||||||
export async function checkManifestStability(
|
export async function checkManifestStability(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
resources: Resource[]
|
resources: Resource[],
|
||||||
|
clusterTyper: ClusterType
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
// Skip if resource type is microsoft.containerservice/fleets
|
||||||
|
if (clusterTyper === ResourceTypeFleet) {
|
||||||
|
core.info(`Skipping checkManifestStability for ${ResourceTypeFleet}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
let rolloutStatusHasErrors = false
|
let rolloutStatusHasErrors = false
|
||||||
for (let i = 0; i < resources.length; i++) {
|
for (let i = 0; i < resources.length; i++) {
|
||||||
const resource = resources[i]
|
const resource = resources[i]
|
||||||
@@ -20,24 +30,35 @@ export async function checkManifestStability(
|
|||||||
try {
|
try {
|
||||||
const result = await kubectl.checkRolloutStatus(
|
const result = await kubectl.checkRolloutStatus(
|
||||||
resource.type,
|
resource.type,
|
||||||
resource.name
|
resource.name,
|
||||||
|
resource.namespace
|
||||||
)
|
)
|
||||||
checkForErrors([result])
|
checkForErrors([result])
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
core.error(ex)
|
core.error(ex)
|
||||||
await kubectl.describe(resource.type, resource.name)
|
await kubectl.describe(
|
||||||
|
resource.type,
|
||||||
|
resource.name,
|
||||||
|
IS_SILENT,
|
||||||
|
resource.namespace
|
||||||
|
)
|
||||||
rolloutStatusHasErrors = true
|
rolloutStatusHasErrors = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resource.type == KubernetesConstants.KubernetesWorkload.POD) {
|
if (resource.type == KubernetesConstants.KubernetesWorkload.POD) {
|
||||||
try {
|
try {
|
||||||
await checkPodStatus(kubectl, resource.name)
|
await checkPodStatus(kubectl, resource)
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
core.warning(
|
core.warning(
|
||||||
`Could not determine pod status: ${JSON.stringify(ex)}`
|
`Could not determine pod status: ${JSON.stringify(ex)}`
|
||||||
)
|
)
|
||||||
await kubectl.describe(resource.type, resource.name)
|
await kubectl.describe(
|
||||||
|
resource.type,
|
||||||
|
resource.name,
|
||||||
|
IS_SILENT,
|
||||||
|
resource.namespace
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -45,14 +66,11 @@ export async function checkManifestStability(
|
|||||||
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE
|
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const service = await getService(kubectl, resource.name)
|
const service = await getService(kubectl, resource)
|
||||||
const {spec, status} = service
|
const {spec, status} = service
|
||||||
if (spec.type === KubernetesConstants.ServiceTypes.LOAD_BALANCER) {
|
if (spec.type === KubernetesConstants.ServiceTypes.LOAD_BALANCER) {
|
||||||
if (!isLoadBalancerIPAssigned(status)) {
|
if (!isLoadBalancerIPAssigned(status)) {
|
||||||
await waitForServiceExternalIPAssignment(
|
await waitForServiceExternalIPAssignment(kubectl, resource)
|
||||||
kubectl,
|
|
||||||
resource.name
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
core.info(
|
core.info(
|
||||||
`ServiceExternalIP ${resource.name} ${status.loadBalancer.ingress[0].ip}`
|
`ServiceExternalIP ${resource.name} ${status.loadBalancer.ingress[0].ip}`
|
||||||
@@ -63,7 +81,12 @@ export async function checkManifestStability(
|
|||||||
core.warning(
|
core.warning(
|
||||||
`Could not determine service status of: ${resource.name} Error: ${ex}`
|
`Could not determine service status of: ${resource.name} Error: ${ex}`
|
||||||
)
|
)
|
||||||
await kubectl.describe(resource.type, resource.name)
|
await kubectl.describe(
|
||||||
|
resource.type,
|
||||||
|
resource.name,
|
||||||
|
IS_SILENT,
|
||||||
|
resource.namespace
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +98,7 @@ export async function checkManifestStability(
|
|||||||
|
|
||||||
export async function checkPodStatus(
|
export async function checkPodStatus(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
podName: string
|
pod: Resource
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const sleepTimeout = 10 * 1000 // 10 seconds
|
const sleepTimeout = 10 * 1000 // 10 seconds
|
||||||
const iterations = 60 // 60 * 10 seconds timeout = 10 minutes max timeout
|
const iterations = 60 // 60 * 10 seconds timeout = 10 minutes max timeout
|
||||||
@@ -85,8 +108,8 @@ export async function checkPodStatus(
|
|||||||
for (let i = 0; i < iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
await sleep(sleepTimeout)
|
await sleep(sleepTimeout)
|
||||||
|
|
||||||
core.debug(`Polling for pod status: ${podName}`)
|
core.debug(`Polling for pod status: ${pod.name}`)
|
||||||
podStatus = await getPodStatus(kubectl, podName)
|
podStatus = await getPodStatus(kubectl, pod)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
podStatus &&
|
podStatus &&
|
||||||
@@ -97,37 +120,42 @@ export async function checkPodStatus(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
podStatus = await getPodStatus(kubectl, podName)
|
podStatus = await getPodStatus(kubectl, pod)
|
||||||
switch (podStatus.phase) {
|
switch (podStatus.phase) {
|
||||||
case 'Succeeded':
|
case 'Succeeded':
|
||||||
case 'Running':
|
case 'Running':
|
||||||
if (isPodReady(podStatus)) {
|
if (isPodReady(podStatus)) {
|
||||||
console.log(`pod/${podName} is successfully rolled out`)
|
console.log(`pod/${pod.name} is successfully rolled out`)
|
||||||
} else {
|
} else {
|
||||||
kubectlDescribeNeeded = true
|
kubectlDescribeNeeded = true
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'Pending':
|
case 'Pending':
|
||||||
if (!isPodReady(podStatus)) {
|
if (!isPodReady(podStatus)) {
|
||||||
core.warning(`pod/${podName} rollout status check timed out`)
|
core.warning(`pod/${pod.name} rollout status check timed out`)
|
||||||
kubectlDescribeNeeded = true
|
kubectlDescribeNeeded = true
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'Failed':
|
case 'Failed':
|
||||||
core.error(`pod/${podName} rollout failed`)
|
core.error(`pod/${pod.name} rollout failed`)
|
||||||
kubectlDescribeNeeded = true
|
kubectlDescribeNeeded = true
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
core.warning(`pod/${podName} rollout status: ${podStatus.phase}`)
|
core.warning(`pod/${pod.name} rollout status: ${podStatus.phase}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kubectlDescribeNeeded) {
|
if (kubectlDescribeNeeded) {
|
||||||
await kubectl.describe('pod', podName)
|
await kubectl.describe(POD, pod.name, IS_SILENT, pod.namespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPodStatus(kubectl: Kubectl, podName: string) {
|
async function getPodStatus(kubectl: Kubectl, pod: Resource) {
|
||||||
const podResult = await kubectl.getResource('pod', podName)
|
const podResult = await kubectl.getResource(
|
||||||
|
POD,
|
||||||
|
pod.name,
|
||||||
|
IS_SILENT,
|
||||||
|
pod.namespace
|
||||||
|
)
|
||||||
checkForErrors([podResult])
|
checkForErrors([podResult])
|
||||||
|
|
||||||
return JSON.parse(podResult.stdout).status
|
return JSON.parse(podResult.stdout).status
|
||||||
@@ -151,10 +179,12 @@ function isPodReady(podStatus: any): boolean {
|
|||||||
return allContainersAreReady
|
return allContainersAreReady
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getService(kubectl: Kubectl, serviceName) {
|
async function getService(kubectl: Kubectl, service: Resource) {
|
||||||
const serviceResult = await kubectl.getResource(
|
const serviceResult = await kubectl.getResource(
|
||||||
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE,
|
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE,
|
||||||
serviceName
|
service.name,
|
||||||
|
IS_SILENT,
|
||||||
|
service.namespace
|
||||||
)
|
)
|
||||||
|
|
||||||
checkForErrors([serviceResult])
|
checkForErrors([serviceResult])
|
||||||
@@ -163,25 +193,25 @@ async function getService(kubectl: Kubectl, serviceName) {
|
|||||||
|
|
||||||
async function waitForServiceExternalIPAssignment(
|
async function waitForServiceExternalIPAssignment(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
serviceName: string
|
service: Resource
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const sleepTimeout = 10 * 1000 // 10 seconds
|
const sleepTimeout = 10 * 1000 // 10 seconds
|
||||||
const iterations = 18 // 18 * 10 seconds timeout = 3 minutes max timeout
|
const iterations = 18 // 18 * 10 seconds timeout = 3 minutes max timeout
|
||||||
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
core.info(`Wait for service ip assignment : ${serviceName}`)
|
core.info(`Wait for service ip assignment : ${service.name}`)
|
||||||
await sleep(sleepTimeout)
|
await sleep(sleepTimeout)
|
||||||
|
|
||||||
const status = (await getService(kubectl, serviceName)).status
|
const status = (await getService(kubectl, service)).status
|
||||||
if (isLoadBalancerIPAssigned(status)) {
|
if (isLoadBalancerIPAssigned(status)) {
|
||||||
core.info(
|
core.info(
|
||||||
`ServiceExternalIP ${serviceName} ${status.loadBalancer.ingress[0].ip}`
|
`ServiceExternalIP ${service.name} ${status.loadBalancer.ingress[0].ip}`
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
core.warning(`Wait for service ip assignment timed out${serviceName}`)
|
core.warning(`Wait for service ip assignment timed out ${service.name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLoadBalancerIPAssigned(status: any) {
|
function isLoadBalancerIPAssigned(status: any) {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import * as fileUtils from './fileUtils'
|
||||||
|
import * as manifestUpdateUtils from './manifestUpdateUtils'
|
||||||
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
describe('manifestUpdateUtils', () => {
|
||||||
|
jest.spyOn(fileUtils, 'moveFileToTmpDir').mockImplementation((filename) => {
|
||||||
|
return path.join('/tmp', filename)
|
||||||
|
})
|
||||||
|
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
|
||||||
|
jest.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
|
||||||
|
return 'test contents'
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should place all files within the temp dir with the same path that they have in the repo', () => {
|
||||||
|
const originalFilePaths: string[] = [
|
||||||
|
'path/in/repo/test.txt',
|
||||||
|
'path/deeper/in/repo/test.txt'
|
||||||
|
]
|
||||||
|
const expected: string[] = [
|
||||||
|
'/tmp/path/in/repo/test.txt',
|
||||||
|
'/tmp/path/deeper/in/repo/test.txt'
|
||||||
|
]
|
||||||
|
const newFilePaths =
|
||||||
|
manifestUpdateUtils.moveFilesToTmpDir(originalFilePaths)
|
||||||
|
expect(newFilePaths).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,7 +3,7 @@ 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'
|
import * as fileHelper from './fileUtils'
|
||||||
import {getTempDirectory} from './fileUtils'
|
import {moveFileToTmpDir} from './fileUtils'
|
||||||
import {
|
import {
|
||||||
InputObjectKindNotDefinedError,
|
InputObjectKindNotDefinedError,
|
||||||
InputObjectMetadataNotDefinedError,
|
InputObjectMetadataNotDefinedError,
|
||||||
@@ -20,16 +20,21 @@ import {
|
|||||||
setImagePullSecrets
|
setImagePullSecrets
|
||||||
} from './manifestPullSecretUtils'
|
} from './manifestPullSecretUtils'
|
||||||
import {Resource} from '../types/kubectl'
|
import {Resource} from '../types/kubectl'
|
||||||
|
import {K8sObject} from '../types/k8sObject'
|
||||||
|
|
||||||
export function updateManifestFiles(manifestFilePaths: string[]) {
|
export function updateManifestFiles(manifestFilePaths: string[]) {
|
||||||
if (manifestFilePaths?.length === 0) {
|
if (manifestFilePaths?.length === 0) {
|
||||||
throw new Error('Manifest files not provided')
|
throw new Error('Manifest files not provided')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// move original set of input files to tmp dir
|
||||||
|
const manifestFilesInTempDir = moveFilesToTmpDir(manifestFilePaths)
|
||||||
|
|
||||||
// update container images
|
// update container images
|
||||||
const containers: string[] = core.getInput('images').split('\n')
|
const containers: string[] = core.getInput('images').split('\n')
|
||||||
|
|
||||||
const manifestFiles = updateContainerImagesInManifestFiles(
|
const manifestFiles = updateContainerImagesInManifestFiles(
|
||||||
manifestFilePaths,
|
manifestFilesInTempDir,
|
||||||
containers
|
containers
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,6 +46,12 @@ export function updateManifestFiles(manifestFilePaths: string[]) {
|
|||||||
return updateImagePullSecretsInManifestFiles(manifestFiles, imagePullSecrets)
|
return updateImagePullSecretsInManifestFiles(manifestFiles, imagePullSecrets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function moveFilesToTmpDir(filepaths: string[]): string[] {
|
||||||
|
return filepaths.map((filename) => {
|
||||||
|
return moveFileToTmpDir(filename)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function UnsetClusterSpecificDetails(resource: any) {
|
export function UnsetClusterSpecificDetails(resource: any) {
|
||||||
if (!resource) {
|
if (!resource) {
|
||||||
return
|
return
|
||||||
@@ -70,12 +81,9 @@ function updateContainerImagesInManifestFiles(
|
|||||||
): string[] {
|
): string[] {
|
||||||
if (filePaths?.length <= 0) return filePaths
|
if (filePaths?.length <= 0) return filePaths
|
||||||
|
|
||||||
const newFilePaths = []
|
|
||||||
|
|
||||||
// update container images
|
// update container images
|
||||||
filePaths.forEach((filePath: string) => {
|
filePaths.forEach((filePath: string) => {
|
||||||
let contents = fs.readFileSync(filePath).toString()
|
let contents = fs.readFileSync(filePath).toString()
|
||||||
|
|
||||||
containers.forEach((container: string) => {
|
containers.forEach((container: string) => {
|
||||||
let [imageName] = container.split(':')
|
let [imageName] = container.split(':')
|
||||||
if (imageName.indexOf('@') > 0) {
|
if (imageName.indexOf('@') > 0) {
|
||||||
@@ -91,13 +99,10 @@ function updateContainerImagesInManifestFiles(
|
|||||||
})
|
})
|
||||||
|
|
||||||
// write updated files
|
// write updated files
|
||||||
const tempDirectory = getTempDirectory()
|
fs.writeFileSync(path.join(filePath), contents)
|
||||||
const fileName = path.join(tempDirectory, path.basename(filePath))
|
|
||||||
fs.writeFileSync(path.join(fileName), contents)
|
|
||||||
newFilePaths.push(fileName)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return newFilePaths
|
return filePaths
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -270,20 +275,29 @@ export function getResources(
|
|||||||
|
|
||||||
const resources: Resource[] = []
|
const resources: Resource[] = []
|
||||||
filePaths.forEach((filePath: string) => {
|
filePaths.forEach((filePath: string) => {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
try {
|
||||||
yaml.safeLoadAll(fileContents, (inputObject) => {
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
const inputObjectKind = inputObject?.kind || ''
|
const inputObjects: K8sObject[] = yaml.loadAll(
|
||||||
if (
|
fileContents
|
||||||
filterResourceTypes.filter(
|
) as K8sObject[]
|
||||||
(type) => inputObjectKind.toLowerCase() === type.toLowerCase()
|
inputObjects.forEach((inputObject) => {
|
||||||
).length > 0
|
const inputObjectKind = inputObject?.kind || ''
|
||||||
) {
|
if (
|
||||||
resources.push({
|
filterResourceTypes.filter(
|
||||||
type: inputObject.kind,
|
(type) => inputObjectKind.toLowerCase() === type.toLowerCase()
|
||||||
name: inputObject.metadata.name
|
).length > 0
|
||||||
})
|
) {
|
||||||
}
|
resources.push({
|
||||||
})
|
type: inputObject.kind,
|
||||||
|
name: inputObject.metadata.name,
|
||||||
|
namespace: inputObject?.metadata?.namespace
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
core.error(`Failed to process file at ${filePath}: ${error.message}`)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return resources
|
return resources
|
||||||
@@ -297,16 +311,21 @@ function updateImagePullSecretsInManifestFiles(
|
|||||||
|
|
||||||
const newObjectsList = []
|
const newObjectsList = []
|
||||||
filePaths.forEach((filePath: string) => {
|
filePaths.forEach((filePath: string) => {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
try {
|
||||||
yaml.safeLoadAll(fileContents, (inputObject: any) => {
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
if (inputObject?.kind) {
|
yaml.loadAll(fileContents, (inputObject: any) => {
|
||||||
const {kind} = inputObject
|
if (inputObject?.kind) {
|
||||||
if (isWorkloadEntity(kind)) {
|
const {kind} = inputObject
|
||||||
updateImagePullSecrets(inputObject, imagePullSecrets)
|
if (isWorkloadEntity(kind)) {
|
||||||
|
updateImagePullSecrets(inputObject, imagePullSecrets)
|
||||||
|
}
|
||||||
|
newObjectsList.push(inputObject)
|
||||||
}
|
}
|
||||||
newObjectsList.push(inputObject)
|
})
|
||||||
}
|
} catch (error) {
|
||||||
})
|
core.error(`Failed to process file at ${filePath}: ${error.message}`)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return fileHelper.writeObjectsToFile(newObjectsList)
|
return fileHelper.writeObjectsToFile(newObjectsList)
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import {cleanLabel} from '../utilities/workflowAnnotationUtils'
|
import {
|
||||||
|
cleanLabel,
|
||||||
|
removeInvalidLabelCharacters,
|
||||||
|
VALID_LABEL_REGEX
|
||||||
|
} from '../utilities/workflowAnnotationUtils'
|
||||||
|
|
||||||
describe('WorkflowAnnotationUtils', () => {
|
describe('WorkflowAnnotationUtils', () => {
|
||||||
describe('cleanLabel', () => {
|
describe('cleanLabel', () => {
|
||||||
@@ -16,5 +20,14 @@ describe('WorkflowAnnotationUtils', () => {
|
|||||||
cleanLabel('Workflow Name / With Slashes / And Spaces')
|
cleanLabel('Workflow Name / With Slashes / And Spaces')
|
||||||
).toEqual('Workflow_Name_-_With_Slashes_-_And_Spaces')
|
).toEqual('Workflow_Name_-_With_Slashes_-_And_Spaces')
|
||||||
})
|
})
|
||||||
|
it('should return a blank string when regex fails (https://github.com/Azure/k8s-deploy/issues/266)', () => {
|
||||||
|
const label = '持续部署'
|
||||||
|
expect(cleanLabel(label)).toEqual('github-workflow-file')
|
||||||
|
|
||||||
|
let removedInvalidChars = removeInvalidLabelCharacters(label)
|
||||||
|
|
||||||
|
const regexResult = VALID_LABEL_REGEX.exec(removedInvalidChars)
|
||||||
|
expect(regexResult).toBe(null)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import {DeploymentConfig} from '../types/deploymentConfig'
|
|||||||
|
|
||||||
const ANNOTATION_PREFIX = 'actions.github.com'
|
const ANNOTATION_PREFIX = 'actions.github.com'
|
||||||
|
|
||||||
|
export const VALID_LABEL_REGEX = /([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]/
|
||||||
|
|
||||||
export function getWorkflowAnnotations(
|
export function getWorkflowAnnotations(
|
||||||
lastSuccessRunSha: string,
|
lastSuccessRunSha: string,
|
||||||
workflowFilePath: string,
|
workflowFilePath: string,
|
||||||
@@ -37,11 +39,17 @@ export function getWorkflowAnnotationKeyLabel(): string {
|
|||||||
* @returns cleaned label
|
* @returns cleaned label
|
||||||
*/
|
*/
|
||||||
export function cleanLabel(label: string): string {
|
export function cleanLabel(label: string): string {
|
||||||
let removedInvalidChars = label
|
let removedInvalidChars = removeInvalidLabelCharacters(label)
|
||||||
|
|
||||||
|
const regexResult = VALID_LABEL_REGEX.exec(removedInvalidChars) || [
|
||||||
|
'github-workflow-file'
|
||||||
|
]
|
||||||
|
return regexResult[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeInvalidLabelCharacters(label: string): string {
|
||||||
|
return label
|
||||||
.replace(/\s/gi, '_')
|
.replace(/\s/gi, '_')
|
||||||
.replace(/[\/\\\|]/gi, '-')
|
.replace(/[\/\\\|]/gi, '-')
|
||||||
.replace(/[^-A-Za-z0-9_.]/gi, '')
|
.replace(/[^-A-Za-z0-9_.]/gi, '')
|
||||||
|
|
||||||
const regex = /([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]/
|
|
||||||
return regex.exec(removedInvalidChars)[0] || ''
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ def delete(kind, name, namespace):
|
|||||||
if (name == "all"):
|
if (name == "all"):
|
||||||
print('kubectl delete --all' + kind + ' -n ' + namespace)
|
print('kubectl delete --all' + kind + ' -n ' + namespace)
|
||||||
deletion = subprocess.Popen(
|
deletion = subprocess.Popen(
|
||||||
['kubectl', 'delete', kind, name, '--namespace', namespace])
|
['kubectl', 'delete', kind, '--all', '--namespace', namespace])
|
||||||
result, err = deletion.communicate()
|
result, err = deletion.communicate()
|
||||||
else:
|
else:
|
||||||
print('kubectl delete ' + kind + ' ' + name + ' -n ' + namespace)
|
print('kubectl delete ' + kind + ' ' + name + ' -n ' + namespace)
|
||||||
@@ -21,7 +21,7 @@ def delete(kind, name, namespace):
|
|||||||
def main():
|
def main():
|
||||||
kind = sys.argv[1]
|
kind = sys.argv[1]
|
||||||
name = sys.argv[2]
|
name = sys.argv[2]
|
||||||
namespace = 'test-' + sys.argv[3]
|
namespace = sys.argv[3]
|
||||||
delete(kind, name, namespace)
|
delete(kind, name, namespace)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -41,10 +41,6 @@ def parseArgs(sysArgs):
|
|||||||
argsDict[labelsKey] = stringListToDict(
|
argsDict[labelsKey] = stringListToDict(
|
||||||
argsDict[labelsKey].split(","), ":")
|
argsDict[labelsKey].split(","), ":")
|
||||||
|
|
||||||
if annotationsKey in argsDict:
|
|
||||||
argsDict[annotationsKey] = stringListToDict(
|
|
||||||
argsDict[annotationsKey].split(","), ":")
|
|
||||||
|
|
||||||
if selectorLabelsKey in argsDict:
|
if selectorLabelsKey in argsDict:
|
||||||
argsDict[selectorLabelsKey] = stringListToDict(
|
argsDict[selectorLabelsKey] = stringListToDict(
|
||||||
argsDict[selectorLabelsKey].split(","), ":")
|
argsDict[selectorLabelsKey].split(","), ":")
|
||||||
@@ -60,6 +56,9 @@ def parseArgs(sysArgs):
|
|||||||
if ingressServicesKey in argsDict:
|
if ingressServicesKey in argsDict:
|
||||||
argsDict[ingressServicesKey] = argsDict[ingressServicesKey].split(",")
|
argsDict[ingressServicesKey] = argsDict[ingressServicesKey].split(",")
|
||||||
|
|
||||||
|
if annotationsKey in argsDict:
|
||||||
|
argsDict[annotationsKey] = argsDict[annotationsKey].split(",")
|
||||||
|
|
||||||
return argsDict
|
return argsDict
|
||||||
|
|
||||||
|
|
||||||
@@ -98,14 +97,14 @@ def verifyDeployment(deployment, parsedArgs):
|
|||||||
return dictMatch, msg
|
return dictMatch, msg
|
||||||
|
|
||||||
if annotationsKey in parsedArgs:
|
if annotationsKey in parsedArgs:
|
||||||
dictMatch, msg = compareDicts(
|
if len(parsedArgs[annotationsKey]) != len(deployment['metadata']['annotations']):
|
||||||
deployment['metadata']['annotations'], parsedArgs[annotationsKey], annotationsKey)
|
return False, f"expected {len(parsedArgs[annotationsKey])} annotations but found {len(deployment['metadata']['annotations'])}"
|
||||||
if not dictMatch:
|
keysPresent, msg = validateKeyPresence(
|
||||||
return dictMatch, msg
|
deployment['metadata']['annotations'], parsedArgs[annotationsKey])
|
||||||
|
if not keysPresent:
|
||||||
|
return keysPresent, msg
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
def verifyService(service, parsedArgs):
|
def verifyService(service, parsedArgs):
|
||||||
# test selector labels, labels, annotations
|
# test selector labels, labels, annotations
|
||||||
if not selectorLabelsKey in parsedArgs:
|
if not selectorLabelsKey in parsedArgs:
|
||||||
@@ -124,10 +123,10 @@ def verifyService(service, parsedArgs):
|
|||||||
return dictMatch, msg
|
return dictMatch, msg
|
||||||
|
|
||||||
if annotationsKey in parsedArgs:
|
if annotationsKey in parsedArgs:
|
||||||
dictMatch, msg = compareDicts(
|
keysPresent, msg = validateKeyPresence(
|
||||||
service['metadata']['annotations'], parsedArgs[annotationsKey], annotationsKey)
|
service['metadata']['annotations'], parsedArgs[annotationsKey])
|
||||||
if not dictMatch:
|
if not keysPresent:
|
||||||
return dictMatch, msg
|
return keysPresent, msg
|
||||||
|
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
@@ -188,6 +187,13 @@ def compareDicts(actual: dict, expected: dict, paramName=""):
|
|||||||
|
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
def validateKeyPresence(actualDict: dict, expectedKeys: list):
|
||||||
|
actualKeys = actualDict.keys()
|
||||||
|
for key in expectedKeys:
|
||||||
|
if key not in actualKeys:
|
||||||
|
return False, f"expected key {key} not found in actual dict. \n actual dict keys {','.join(actualKeys)}"
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parsedArgs: dict = parseArgs(sys.argv[1:])
|
parsedArgs: dict = parseArgs(sys.argv[1:])
|
||||||
@@ -220,14 +226,13 @@ def main():
|
|||||||
|
|
||||||
if k8_object == None:
|
if k8_object == None:
|
||||||
raise ValueError(f"{kind} {name} was not found")
|
raise ValueError(f"{kind} {name} was not found")
|
||||||
|
|
||||||
except:
|
except:
|
||||||
msg = kind+' '+name+' not created or not found'
|
msg = kind+' '+name+' not created or not found'
|
||||||
getAllObjectsCmd = azPrefix + 'kubectl get '+kind+' -n '+namespace
|
getAllObjectsCmd = azPrefix + 'kubectl get '+kind+' -n '+namespace
|
||||||
if not azPrefix == "":
|
if not azPrefix == "":
|
||||||
getAllObjectsCmd = azPrefix + "'{getAllObjectsCmd}'" # add extra set of quotes
|
getAllObjectsCmd = azPrefix + "'{getAllObjectsCmd}'" # add extra set of quotes
|
||||||
cmd = + "'" + cmd + "'"
|
foundObjects = os.popen(getAllObjectsCmd).read()
|
||||||
foundObjects = os.popen().read()
|
|
||||||
suffix = f"resources of type {kind}: {foundObjects}"
|
suffix = f"resources of type {kind}: {foundObjects}"
|
||||||
sys.exit(msg + " " + suffix)
|
sys.exit(msg + " " + suffix)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-deployment3
|
||||||
|
labels:
|
||||||
|
app: nginx3
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: nginx3
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx3
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: nginx-service3
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: nginx3
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-deployment2
|
||||||
|
labels:
|
||||||
|
app: nginx2
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: nginx2
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx2
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: nginx-service2
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: nginx2
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"module": "commonjs"
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "test", "src/**/*.test.ts"]
|
"exclude": ["node_modules", "test", "src/**/*.test.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user