mirror of
https://github.com/Azure/k8s-deploy.git
synced 2026-06-22 03:09:27 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef8692a58f | |||
| ca3f299adf | |||
| b75cce76a5 | |||
| 922d445a51 | |||
| 8d3326c0a3 |
@@ -1,36 +0,0 @@
|
|||||||
name: Bug Report
|
|
||||||
description: File a bug report specifying all inputs you provided for the action, we will respond to this thread with any questions.
|
|
||||||
title: 'Bug: '
|
|
||||||
labels: ['bug', 'triage']
|
|
||||||
assignees: '@Azure/aks-atlanta'
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: What-happened
|
|
||||||
attributes:
|
|
||||||
label: What happened?
|
|
||||||
description: Tell us what happened and how is it different from the expected?
|
|
||||||
placeholder: Tell us what you see!
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
|
||||||
id: Version
|
|
||||||
attributes:
|
|
||||||
label: Version
|
|
||||||
options:
|
|
||||||
- label: I am using the latest version
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: Runner
|
|
||||||
attributes:
|
|
||||||
label: Runner
|
|
||||||
description: What runner are you using?
|
|
||||||
placeholder: Mention the runner info (self-hosted, operating system)
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: Logs
|
|
||||||
attributes:
|
|
||||||
label: Relevant log output
|
|
||||||
description: Run in debug mode for the most verbose logs. Please feel free to attach a screenshot of the logs
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: GitHub Action "k8s-deploy" Support
|
|
||||||
url: https://github.com/Azure/k8s-deploy
|
|
||||||
security: https://github.com/Azure/k8s-deploy/blob/main/SECURITY.md
|
|
||||||
about: Please ask and answer questions here.
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
name: Feature Request
|
|
||||||
description: File a Feature Request form, we will respond to this thread with any questions.
|
|
||||||
title: 'Feature Request: '
|
|
||||||
labels: ['Feature']
|
|
||||||
assignees: '@Azure/aks-atlanta'
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: Feature_request
|
|
||||||
attributes:
|
|
||||||
label: Feature request
|
|
||||||
description: Provide example functionality and links to relevant docs
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
@@ -13,15 +13,20 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
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@v2
|
uses: github/codeql-action/init@v1
|
||||||
# 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
|
||||||
@@ -29,7 +34,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@v2
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
# ℹ️ 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
|
||||||
@@ -43,4 +48,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v1
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
name: Minikube Integration Tests - basic
|
|
||||||
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@v3
|
|
||||||
|
|
||||||
- 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@v3
|
|
||||||
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@v2
|
|
||||||
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
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if deployments and services were created
|
|
||||||
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=Service name=nginx-service labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_basic selectorLabels=app:nginx
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
name: Minikube Integration Tests - blue-green ingress
|
|
||||||
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@v3
|
|
||||||
|
|
||||||
- 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@v3
|
|
||||||
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@v2
|
|
||||||
name: Install Python
|
|
||||||
with:
|
|
||||||
python-version: '3.x'
|
|
||||||
|
|
||||||
- name: Cleaning any previously created items
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service' ${{ env.NAMESPACE }}
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service-green' ${{ env.NAMESPACE }}
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Deployment' 'nginx-deployment-green' ${{ env.NAMESPACE }}
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Deployment' 'nginx-deployment' ${{ env.NAMESPACE }}
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Ingress' 'nginx-ingress' ${{ env.NAMESPACE }}
|
|
||||||
|
|
||||||
- name: Executing deploy action for ingress
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-ingress.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: ingress
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if deployments, services and ingresses were created with green labels and original tag
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-green containerName=nginx:1.14.2 labels=k8s.deploy.color:green,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-green labels=k8s.deploy.color:green,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Ingress name=nginx-ingress ingressServices=nginx-service-green,unrouted-service
|
|
||||||
|
|
||||||
- name: Executing promote action for ingress
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-ingress.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: ingress
|
|
||||||
action: promote
|
|
||||||
|
|
||||||
- name: Checking if deployments, services and ingresses were created with none labels after first promote
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=k8s.deploy.color:None,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Ingress name=nginx-ingress ingressServices=nginx-service,unrouted-service
|
|
||||||
|
|
||||||
- name: Executing second deploy action for ingress with new tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:latest
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-ingress.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: ingress
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if deployments (with new tag), services and ingresses were created with green labels after deploy, and old deployment persists
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-green containerName=nginx:latest labels=k8s.deploy.color:green,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-green labels=k8s.deploy.color:green,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=k8s.deploy.color:None,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Ingress name=nginx-ingress ingressServices=nginx-service-green,unrouted-service
|
|
||||||
|
|
||||||
- name: Executing second promote action for ingress now using new image tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:latest
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-ingress.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: ingress
|
|
||||||
action: promote
|
|
||||||
|
|
||||||
- name: Checking if deployments, services and ingresses were created with none labels after promote for new tag
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=k8s.deploy.color:None,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Ingress name=nginx-ingress ingressServices=nginx-service,unrouted-service
|
|
||||||
|
|
||||||
- name: Executing deploy action for ingress to be rejected using old tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-ingress.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: ingress
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if new deployments (with old tag), services and ingresses were created with green labels after deploy, and old deployment (with latest tag) persists
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-green containerName=nginx:1.14.2 labels=k8s.deploy.color:green,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-green labels=k8s.deploy.color:green,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=k8s.deploy.color:None,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Ingress name=nginx-ingress ingressServices=nginx-service-green,unrouted-service
|
|
||||||
|
|
||||||
- name: Executing reject action for ingress to reject new deployment with 1.14.2 tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-ingress.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: ingress
|
|
||||||
action: reject
|
|
||||||
|
|
||||||
# MAY BE USEFUL TO ADD AN ANTI-CHECK - CHECK TO MAKE SURE CERTAIN OBJECTS DON'T EXIST
|
|
||||||
- name: Checking if deployments, services and ingresses were created with none labels and latest tag after reject
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=k8s.deploy.color:None,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_ingress selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Ingress name=nginx-ingress ingressServices=nginx-service,unrouted-service
|
|
||||||
|
|
||||||
- name: Cleaning up current set up
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service' ${{ env.NAMESPACE }}
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Deployment' 'nginx-deployment' ${{ env.NAMESPACE }}
|
|
||||||
|
|
||||||
- if: ${{ always() }}
|
|
||||||
name: Delete created namespace
|
|
||||||
run: kubectl delete ns ${{ env.NAMESPACE }}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
name: Minikube Integration Tests - blue-green service
|
|
||||||
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@v3
|
|
||||||
|
|
||||||
- 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@v3
|
|
||||||
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@v2
|
|
||||||
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 service
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: service
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if deployments and services were created with green labels and original tag
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-green containerName=nginx:1.14.2 labels=k8s.deploy.color:green,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=k8s.deploy.color:green,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
- name: Executing promote action for service
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: service
|
|
||||||
action: promote
|
|
||||||
|
|
||||||
- name: Checking if deployments and services were created with none labels after first promote
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=k8s.deploy.color:None,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
- name: Executing second deploy action for service with new tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:latest
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: service
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if deployments (with new tag) and services were created with green labels after deploy, and old deployment persists
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-green containerName=nginx:latest labels=k8s.deploy.color:green,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=k8s.deploy.color:green,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
|
|
||||||
- name: Executing second promote action for service now using new image tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:latest
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: service
|
|
||||||
action: promote
|
|
||||||
|
|
||||||
- name: Checking if deployments and services were created with none labels after promote for new tag
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=k8s.deploy.color:None,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
- name: Executing deploy action for service to be rejected using old tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: service
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if new deployments (with old tag) and services were created with green labels after deploy, and old deployment (with latest tag) persists
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-green containerName=nginx:1.14.2 labels=k8s.deploy.color:green,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=k8s.deploy.color:green,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
|
|
||||||
- name: Executing reject action for service to reject new deployment with 1.14.2 tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: service
|
|
||||||
action: reject
|
|
||||||
|
|
||||||
# MAY BE USEFUL TO ADD AN ANTI-CHECK - CHECK TO MAKE SURE CERTAIN OBJECTS DON'T EXIST
|
|
||||||
- name: Checking if deployments and services were created with none labels and latest tag after reject
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service labels=k8s.deploy.color:None,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_service selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
- name: Cleaning up current set up
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service' ${{ env.NAMESPACE }}
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Deployment' 'nginx-deployment' ${{ env.NAMESPACE }}
|
|
||||||
|
|
||||||
- if: ${{ always() }}
|
|
||||||
name: Delete created namespace
|
|
||||||
run: kubectl delete ns ${{ env.NAMESPACE }}
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
name: Minikube Integration Tests - blue-green SMI
|
|
||||||
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@v3
|
|
||||||
|
|
||||||
- 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@v3
|
|
||||||
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: Install linkerd and add controlplane to cluster
|
|
||||||
run: |
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh
|
|
||||||
curl -sL https://linkerd.github.io/linkerd-smi/install | sh
|
|
||||||
export PATH=$PATH:/home/runner/.linkerd2/bin
|
|
||||||
|
|
||||||
linkerd install --crds | kubectl apply -f -
|
|
||||||
linkerd install --set proxyInit.runAsRoot=true | kubectl apply -f -
|
|
||||||
linkerd smi install | kubectl apply -f -
|
|
||||||
|
|
||||||
- name: Create namespace to run tests
|
|
||||||
run: kubectl create ns ${{ env.NAMESPACE }}
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
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 }}
|
|
||||||
python test/integration/k8s-deploy-delete.py 'TrafficSplit' 'all' ${{ env.NAMESPACE }}
|
|
||||||
|
|
||||||
- name: Executing deploy action for smi
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: smi
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if deployments, services, and ts objects were created with green labels and original tag
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-green containerName=nginx:1.14.2 labels=k8s.deploy.color:green,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
|
|
||||||
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_-_blue-green_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-stable labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI,k8s.deploy.color:None selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-green labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI,k8s.deploy.color:green selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-trafficsplit tsServices=nginx-service-stable:0,nginx-service-green:100
|
|
||||||
|
|
||||||
- name: Executing promote action for smi
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: smi
|
|
||||||
action: promote
|
|
||||||
|
|
||||||
# another good place for anti-test - ensure old deps are deleted after promote
|
|
||||||
- name: Checking if deployments, services, and ts objects were created with none labels after first promote
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
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_-_blue-green_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-stable labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI,k8s.deploy.color:None selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-trafficsplit tsServices=nginx-service-stable:100,nginx-service-green:0
|
|
||||||
|
|
||||||
- name: Executing second deploy action for smi with new tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:latest
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: smi
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if deployments (with new tag) and services were created with green labels after deploy, and old deployment persists
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-green containerName=nginx:latest labels=k8s.deploy.color:green,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
|
|
||||||
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_-_blue-green_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-stable labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI,k8s.deploy.color:None selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-green labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI,k8s.deploy.color:green selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-trafficsplit tsServices=nginx-service-stable:0,nginx-service-green:100
|
|
||||||
|
|
||||||
- name: Executing second promote action for smi now using new image tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:latest
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: smi
|
|
||||||
action: promote
|
|
||||||
|
|
||||||
- name: Checking if deployments and services were created with none labels after promote for new tag, ts is stable
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
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_-_blue-green_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-stable labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI,k8s.deploy.color:None selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-trafficsplit tsServices=nginx-service-stable:100,nginx-service-green:0
|
|
||||||
|
|
||||||
- name: Executing deploy action for smi to be rejected using old tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: smi
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if new deployments (with old tag) and services were created with green labels after deploy, and old deployment (with latest tag) persists
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-green containerName=nginx:1.14.2 labels=k8s.deploy.color:green,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
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_-_blue-green_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-stable labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI,k8s.deploy.color:None selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-green labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI,k8s.deploy.color:green selectorLabels=app:nginx,k8s.deploy.color:green
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-trafficsplit tsServices=nginx-service-stable:0,nginx-service-green:100
|
|
||||||
|
|
||||||
- name: Executing reject action for smi to reject new deployment with 1.14.2 tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/blue-green/test-service.yml
|
|
||||||
strategy: blue-green
|
|
||||||
route-method: smi
|
|
||||||
action: reject
|
|
||||||
|
|
||||||
# MAY BE USEFUL TO ADD AN ANTI-CHECK - CHECK TO MAKE SURE CERTAIN OBJECTS DON'T EXIST
|
|
||||||
- name: Checking if deployments and services were created with none labels and latest tag after reject
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=k8s.deploy.color:None,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
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_-_blue-green_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-stable labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_blue-green_SMI,k8s.deploy.color:None selectorLabels=app:nginx,k8s.deploy.color:None
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-trafficsplit tsServices=nginx-service-stable:100,nginx-service-green:0
|
|
||||||
|
|
||||||
- name: Cleaning up current set up
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service' ${{ env.NAMESPACE }}
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Deployment' 'nginx-deployment' ${{ env.NAMESPACE }}
|
|
||||||
|
|
||||||
- if: ${{ always() }}
|
|
||||||
name: Delete created namespace
|
|
||||||
run: kubectl delete ns ${{ env.NAMESPACE }}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
name: Minikube Integration Tests - canary pod
|
|
||||||
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@v3
|
|
||||||
|
|
||||||
- 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@v3
|
|
||||||
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@v2
|
|
||||||
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
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: pod
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if deployments and services were created with canary labels and original tag
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-canary containerName=nginx:1.14.2 labels=workflow/version:canary,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_pod selectorLabels=app:nginx,workflow/version:canary
|
|
||||||
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_-_canary_pod selectorLabels=app:nginx
|
|
||||||
|
|
||||||
- name: Executing promote action for pod
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: pod
|
|
||||||
action: promote
|
|
||||||
|
|
||||||
# another good place for anti-test - ensure old deps are deleted after promote
|
|
||||||
- name: Checking if deployments and services were created with stable labels after first promote
|
|
||||||
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_-_canary_pod 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_-_canary_pod selectorLabels=app:nginx
|
|
||||||
|
|
||||||
- name: Executing second deploy action for pod with new tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:latest
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: pod
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if deployments (with new tag) and services were created with canary labels after deploy, and old deployment persists
|
|
||||||
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_-_canary_pod selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-canary containerName=nginx:latest labels=workflow/version:canary,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_pod selectorLabels=app:nginx,workflow/version:canary
|
|
||||||
|
|
||||||
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_-_canary_pod selectorLabels=app:nginx
|
|
||||||
|
|
||||||
- name: Executing second promote action for pod now using new image tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:latest
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: pod
|
|
||||||
action: promote
|
|
||||||
|
|
||||||
- name: Checking if deployments and services were created with stable labels after promote for new tag, ts is stable
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_pod 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_-_canary_pod selectorLabels=app:nginx
|
|
||||||
|
|
||||||
- name: Executing deploy action for pod to be rejected using old tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: pod
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if new deployments (with old tag) and services were created with canary and baseline labels after deploy, and stable deployment (with latest tag) persists
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-canary containerName=nginx:1.14.2 labels=workflow/version:canary,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_pod selectorLabels=app:nginx,workflow/version:canary
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_pod 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_-_canary_pod selectorLabels=app:nginx
|
|
||||||
|
|
||||||
- name: Executing reject action for pod to reject new deployment with 1.14.2 tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: pod
|
|
||||||
action: reject
|
|
||||||
|
|
||||||
# MAY BE USEFUL TO ADD AN ANTI-CHECK - CHECK TO MAKE SURE CERTAIN OBJECTS DON'T EXIST
|
|
||||||
- name: Checking if deployments and services were created with stable labels and latest tag after reject
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:latest labels=app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_pod 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_-_canary_pod selectorLabels=app:nginx
|
|
||||||
|
|
||||||
- name: Cleaning up current set up
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service' ${{ env.NAMESPACE }}
|
|
||||||
python test/integration/k8s-deploy-delete.py 'Deployment' 'nginx-deployment' ${{ env.NAMESPACE }}
|
|
||||||
|
|
||||||
- if: ${{ always() }}
|
|
||||||
name: Delete created namespace
|
|
||||||
run: kubectl delete ns ${{ env.NAMESPACE }}
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
name: Minikube Integration Tests - canary SMI
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- 'releases/*'
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- 'releases/*'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
run-integration-test:
|
|
||||||
name: Run Minikube Integration Tests
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
env:
|
|
||||||
KUBECONFIG: /home/runner/.kube/config
|
|
||||||
NAMESPACE: test-${{ github.run_id }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- 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@v3
|
|
||||||
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: Install linkerd and add controlplane to cluster
|
|
||||||
run: |
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh
|
|
||||||
curl -sL https://linkerd.github.io/linkerd-smi/install | sh
|
|
||||||
export PATH=$PATH:/home/runner/.linkerd2/bin
|
|
||||||
|
|
||||||
linkerd install --crds | kubectl apply -f -
|
|
||||||
linkerd install --set proxyInit.runAsRoot=true | kubectl apply -f -
|
|
||||||
linkerd smi install | kubectl apply -f -
|
|
||||||
|
|
||||||
- name: Create namespace to run tests
|
|
||||||
run: kubectl create ns ${{ env.NAMESPACE }}
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
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 }}
|
|
||||||
python test/integration/k8s-deploy-delete.py 'TrafficSplit' 'all' ${{ env.NAMESPACE }}
|
|
||||||
|
|
||||||
- name: Executing deploy action for smi
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: smi
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if deployments, services, and ts objects were created with canary labels and original tag
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-canary containerName=nginx:1.14.2 labels=workflow/version:canary,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI selectorLabels=app:nginx,workflow/version:canary
|
|
||||||
|
|
||||||
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_-_canary_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-canary labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI,workflow/version:canary selectorLabels=app:nginx,workflow/version:canary
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-workflow-rollout tsServices=nginx-service-stable:0,nginx-service-canary:1000,nginx-service-baseline:0
|
|
||||||
|
|
||||||
- name: Executing promote action for smi
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: smi
|
|
||||||
action: promote
|
|
||||||
|
|
||||||
# another good place for anti-test - ensure old deps are deleted after promote
|
|
||||||
- name: Checking if deployments, services, and ts objects were created with stable labels after first promote
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-stable containerName=nginx:1.14.2 labels=workflow/version:stable,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI selectorLabels=app:nginx,workflow/version:stable
|
|
||||||
|
|
||||||
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_-_canary_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-stable labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI,workflow/version:stable selectorLabels=app:nginx,workflow/version:stable
|
|
||||||
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-workflow-rollout tsServices=nginx-service-stable:1000,nginx-service-canary:0,nginx-service-baseline:0
|
|
||||||
|
|
||||||
- name: Executing second deploy action for smi with new tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:latest
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: smi
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if deployments (with new tag) and services were created with canary labels after deploy, and old deployment persists
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-stable containerName=nginx:1.14.2 labels=workflow/version:stable,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI selectorLabels=app:nginx,workflow/version:stable
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-canary containerName=nginx:latest labels=workflow/version:canary,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI selectorLabels=app:nginx,workflow/version:canary
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-baseline containerName=nginx:1.14.2 labels=workflow/version:baseline,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI selectorLabels=app:nginx,workflow/version:baseline
|
|
||||||
|
|
||||||
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_-_canary_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-stable labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI,workflow/version:stable selectorLabels=app:nginx,workflow/version:stable
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-canary labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI,workflow/version:canary selectorLabels=app:nginx,workflow/version:canary
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-baseline labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI,workflow/version:baseline selectorLabels=app:nginx,workflow/version:baseline
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-workflow-rollout tsServices=nginx-service-stable:500,nginx-service-canary:250,nginx-service-baseline:250
|
|
||||||
|
|
||||||
- name: Executing second promote action for smi now using new image tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:latest
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: smi
|
|
||||||
action: promote
|
|
||||||
|
|
||||||
- name: Checking if deployments and services were created with stable labels after promote for new tag, ts is stable
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-stable containerName=nginx:latest labels=workflow/version:stable,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI selectorLabels=app:nginx,workflow/version:stable
|
|
||||||
|
|
||||||
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_-_canary_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-stable labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI,workflow/version:stable selectorLabels=app:nginx,workflow/version:stable
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-workflow-rollout tsServices=nginx-service-stable:1000,nginx-service-canary:0,nginx-service-baseline:0
|
|
||||||
|
|
||||||
- name: Executing deploy action for smi to be rejected using old tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: smi
|
|
||||||
action: deploy
|
|
||||||
|
|
||||||
- name: Checking if new deployments (with old tag) and services were created with canary and baseline labels after deploy, and stable deployment (with latest tag) persists
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-canary containerName=nginx:1.14.2 labels=workflow/version:canary,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI selectorLabels=app:nginx,workflow/version:canary
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-baseline containerName=nginx:latest labels=workflow/version:baseline,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI selectorLabels=app:nginx,workflow/version:baseline
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-stable containerName=nginx:latest labels=workflow/version:stable,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI selectorLabels=app:nginx,workflow/version:stable
|
|
||||||
|
|
||||||
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_-_canary_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-stable labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI,workflow/version:stable selectorLabels=app:nginx,workflow/version:stable
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-baseline labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI,workflow/version:baseline selectorLabels=app:nginx,workflow/version:baseline
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-canary labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI,workflow/version:canary selectorLabels=app:nginx,workflow/version:canary
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-workflow-rollout tsServices=nginx-service-stable:500,nginx-service-canary:250,nginx-service-baseline:250
|
|
||||||
|
|
||||||
- name: Executing reject action for smi to reject new deployment with 1.14.2 tag
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
strategy: canary
|
|
||||||
percentage: 50
|
|
||||||
traffic-split-method: smi
|
|
||||||
action: reject
|
|
||||||
|
|
||||||
# MAY BE USEFUL TO ADD AN ANTI-CHECK - CHECK TO MAKE SURE CERTAIN OBJECTS DON'T EXIST
|
|
||||||
- name: Checking if deployments and services were created with stable labels and latest tag after reject
|
|
||||||
run: |
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment-stable containerName=nginx:latest labels=workflow/version:stable,app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI selectorLabels=app:nginx,workflow/version:stable
|
|
||||||
|
|
||||||
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_-_canary_SMI selectorLabels=app:nginx
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Service name=nginx-service-stable labels=workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_canary_SMI,workflow/version:stable selectorLabels=app:nginx,workflow/version:stable
|
|
||||||
|
|
||||||
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=TrafficSplit name=nginx-service-workflow-rollout tsServices=nginx-service-stable:1000,nginx-service-canary:0,nginx-service-baseline:0
|
|
||||||
|
|
||||||
- name: Cleaning up current set up
|
|
||||||
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 }}
|
|
||||||
python test/integration/k8s-deploy-delete.py 'TrafficSplit' 'all' ${{ env.NAMESPACE }}
|
|
||||||
|
|
||||||
- if: ${{ always() }}
|
|
||||||
name: Delete created namespace
|
|
||||||
run: kubectl delete ns ${{ env.NAMESPACE }}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
name: Cluster Integration Tests - private cluster
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- 'releases/*'
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
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 }}
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
id-token: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
rm -rf node_modules/
|
|
||||||
npm install
|
|
||||||
- name: Install ncc
|
|
||||||
run: npm i -g @vercel/ncc
|
|
||||||
- name: Build
|
|
||||||
run: ncc build src/run.ts -o lib
|
|
||||||
- name: Azure login
|
|
||||||
uses: azure/login@v1.4.3
|
|
||||||
with:
|
|
||||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
|
||||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
|
||||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
|
||||||
|
|
||||||
- uses: Azure/setup-kubectl@v3
|
|
||||||
name: Install Kubectl
|
|
||||||
|
|
||||||
- name: Create private AKS cluster and set context
|
|
||||||
run: |
|
|
||||||
set +x
|
|
||||||
# create cluster
|
|
||||||
az group create --location eastus --name ${{ env.NAMESPACE }}
|
|
||||||
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 }}
|
|
||||||
|
|
||||||
- name: Create namespace to run tests
|
|
||||||
run: |
|
|
||||||
az aks command invoke --resource-group ${{ env.NAMESPACE }} --name ${{ env.NAMESPACE }} --command "kubectl create ns ${{ env.NAMESPACE }}"
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
name: Install Python
|
|
||||||
with:
|
|
||||||
python-version: '3.x'
|
|
||||||
|
|
||||||
- name: Executing deploy action for pod
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
namespace: ${{ env.NAMESPACE }}
|
|
||||||
images: nginx:1.14.2
|
|
||||||
manifests: |
|
|
||||||
test/integration/manifests/test.yml
|
|
||||||
action: deploy
|
|
||||||
private-cluster: true
|
|
||||||
resource-group: ${{ env.NAMESPACE }}
|
|
||||||
name: ${{ env.NAMESPACE }}
|
|
||||||
|
|
||||||
- name: Checking if deployments and services were created
|
|
||||||
run: |
|
|
||||||
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
|
|
||||||
|
|
||||||
- name: Clean up AKS cluster
|
|
||||||
if: ${{ always() }}
|
|
||||||
run: |
|
|
||||||
echo "deleting AKS cluster and resource group"
|
|
||||||
az aks delete --yes --resource-group ${{ env.NAMESPACE }} --name ${{ env.NAMESPACE }}
|
|
||||||
az group delete --resource-group ${{ env.NAMESPACE }} --yes
|
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
name: Minikube Integration Tests
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- main
|
||||||
|
- 'releases/*'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- main
|
||||||
|
- 'releases/*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-integration-test:
|
||||||
|
name: Run Minikube Integration Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /home/runner/.kube/config
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
rm -rf node_modules/
|
||||||
|
npm install
|
||||||
|
|
||||||
|
- name: Install ncc
|
||||||
|
run: npm i -g @vercel/ncc
|
||||||
|
- name: Build
|
||||||
|
run: ncc build src/run.ts -o lib
|
||||||
|
|
||||||
|
- name: Set name of ns
|
||||||
|
run: echo "::set-output name=name::$(echo `date +%Y%m%d%H%M%S`)"
|
||||||
|
shell: bash
|
||||||
|
id: ns
|
||||||
|
|
||||||
|
- uses: Azure/setup-kubectl@v1
|
||||||
|
name: Install Kubectl
|
||||||
|
|
||||||
|
- id: setup-minikube
|
||||||
|
name: Setup Minikube
|
||||||
|
uses: manusa/actions-setup-minikube@v2.4.2
|
||||||
|
with:
|
||||||
|
minikube version: 'v1.24.0'
|
||||||
|
kubernetes version: 'v1.17.8'
|
||||||
|
driver: 'none'
|
||||||
|
timeout-minutes: 3
|
||||||
|
|
||||||
|
- name: Create namespace to run tests
|
||||||
|
run: kubectl create ns test-${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
name: Install Python
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
|
||||||
|
- name: Cleaning any previously created items
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service-green' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-delete.py 'Deployment' 'nginx-deployment-green' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-delete.py 'Deployment' 'nginx-deployment' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-delete.py 'Ingress' 'nginx-ingress' ${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- name: Executing deploy action
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
namespace: test-${{ steps.ns.outputs.name }}
|
||||||
|
images: nginx:1.14.2
|
||||||
|
manifests: |
|
||||||
|
test/integration/manifests/test-service.yml
|
||||||
|
strategy: blue-green
|
||||||
|
route-method: service
|
||||||
|
action: deploy
|
||||||
|
|
||||||
|
- name: Checking if deploments and services were created with green labels
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-test.py 'Deployment' 'nginx-deployment-green' 'green' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Service' 'nginx-service' 'green' ${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- name: Executing promote action
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
namespace: test-${{ steps.ns.outputs.name }}
|
||||||
|
images: nginx:1.14.2
|
||||||
|
manifests: |
|
||||||
|
test/integration/manifests/test-service.yml
|
||||||
|
strategy: blue-green
|
||||||
|
route-method: service
|
||||||
|
action: promote
|
||||||
|
|
||||||
|
- name: Checking if deploments and services were created with none labels after promote
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-test.py 'Deployment' 'nginx-deployment' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Service' 'nginx-service' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- name: Executing deploy action on
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
namespace: test-${{ steps.ns.outputs.name }}
|
||||||
|
images: nginx:1.19.1
|
||||||
|
manifests: |
|
||||||
|
test/integration/manifests/test-service.yml
|
||||||
|
strategy: blue-green
|
||||||
|
route-method: service
|
||||||
|
action: deploy
|
||||||
|
|
||||||
|
- name: Checking if deploments and services were created with green labels, and old workloads persist on deploy
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-test.py 'Deployment' 'nginx-deployment-green' 'green' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Service' 'nginx-service' 'green' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Deployment' 'nginx-deployment' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- name: Executing reject action
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
namespace: test-${{ steps.ns.outputs.name }}
|
||||||
|
images: nginx:1.19.1
|
||||||
|
manifests: |
|
||||||
|
test/integration/manifests/test-service.yml
|
||||||
|
strategy: blue-green
|
||||||
|
route-method: service
|
||||||
|
action: reject
|
||||||
|
|
||||||
|
- name: Checking if deploments and services were routed back to none labels after reject
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-test.py 'Deployment' 'nginx-deployment' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Service' 'nginx-service' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- name: Cleaning up current set up
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-delete.py 'Service' 'nginx-service' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-delete.py 'Deployment' 'nginx-deployment' ${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- name: Executing deploy action for ingress
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
namespace: test-${{ steps.ns.outputs.name }}
|
||||||
|
images: nginx:1.14.2
|
||||||
|
manifests: |
|
||||||
|
test/integration/manifests/test-ingress.yml
|
||||||
|
strategy: blue-green
|
||||||
|
route-method: ingress
|
||||||
|
action: deploy
|
||||||
|
|
||||||
|
- name: Checking if deploments, services and ingresses were created with green labels
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-test.py 'Deployment' 'nginx-deployment-green' 'green' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Service' 'nginx-service-green' 'green' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Ingress' 'nginx-ingress' 'green' ${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- name: Executing promote action for ingress
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
namespace: test-${{ steps.ns.outputs.name }}
|
||||||
|
images: nginx:1.14.2
|
||||||
|
manifests: |
|
||||||
|
test/integration/manifests/test-ingress.yml
|
||||||
|
strategy: blue-green
|
||||||
|
route-method: ingress
|
||||||
|
action: promote
|
||||||
|
|
||||||
|
- name: Checking if deploments, services and ingresses were created with none labels after promote
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-test.py 'Deployment' 'nginx-deployment' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Service' 'nginx-service' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Ingress' 'nginx-ingress' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- name: Executing deploy action for ingress
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
namespace: test-${{ steps.ns.outputs.name }}
|
||||||
|
images: nginx:1.19.1
|
||||||
|
manifests: |
|
||||||
|
test/integration/manifests/test-ingress.yml
|
||||||
|
strategy: blue-green
|
||||||
|
route-method: ingress
|
||||||
|
action: deploy
|
||||||
|
|
||||||
|
- name: Checking if deploments, services and ingresses were created with green labels after deploy, and old deployment persists
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-test.py 'Deployment' 'nginx-deployment-green' 'green' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Deployment' 'nginx-deployment' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Service' 'nginx-service' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Service' 'nginx-service-green' 'green' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Ingress' 'nginx-ingress' 'green' ${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- name: Executing reject action for ingress
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
namespace: test-${{ steps.ns.outputs.name }}
|
||||||
|
images: nginx:1.19.1
|
||||||
|
manifests: |
|
||||||
|
test/integration/manifests/test-ingress.yml
|
||||||
|
strategy: blue-green
|
||||||
|
route-method: ingress
|
||||||
|
action: reject
|
||||||
|
|
||||||
|
- name: Checking if deploments, services and ingresses were created with none labels after reject
|
||||||
|
run: |
|
||||||
|
python test/integration/k8s-deploy-test.py 'Deployment' 'nginx-deployment' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Service' 'nginx-service' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
python test/integration/k8s-deploy-test.py 'Ingress' 'nginx-ingress' 'None' ${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- if: ${{ always() }}
|
||||||
|
name: Delete created namespace
|
||||||
|
run: kubectl delete ns test-${{ steps.ns.outputs.name }}
|
||||||
|
|
||||||
|
- if: ${{ always() }}
|
||||||
|
name: Posting result back to PR
|
||||||
|
run: |
|
||||||
|
if [ '${{ steps.job-type.outputs.type }}' == 'pr' ]; then ruby postStatus.rb ${{github.event.client_payload.repository}} ${{github.event.client_payload.commit}} ${{secrets.L2_REPO_TOKEN}} ${{job.status}} ${{github.run_id}} ${{matrix.os}} false ${{ secrets.L2_REPO_USER }}; fi
|
||||||
|
shell: bash
|
||||||
@@ -11,10 +11,9 @@ 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@v3
|
- uses: actions/checkout@v1
|
||||||
- run: |
|
- run: |
|
||||||
npm install
|
npm install
|
||||||
npm test
|
npm test
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ node_modules
|
|||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.idea
|
.idea
|
||||||
|
lib/
|
||||||
|
|
||||||
coverage/
|
coverage/
|
||||||
@@ -51,7 +51,7 @@ Following are the key capabilities of this action:
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>manifests </br></br>(Required)</td>
|
<td>manifests </br></br>(Required)</td>
|
||||||
<td>Path to the manifest files to be used for deployment. These can also be directories containing manifest files, in which case, all manifest files in the referenced directory at every depth will be deployed, or URLs to manifest files (like https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/controllers/nginx-deployment.yaml). Files and URLs not ending in .yml or .yaml will be ignored.</td>
|
<td>Path to the manifest files to be used for deployment. These can also be directories containing manifest files, in which case, all manifest files in the referenced directory at every depth will be deployed. Files not ending in .yml or .yaml will be ignored.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>strategy </br></br>(Required)</td>
|
<td>strategy </br></br>(Required)</td>
|
||||||
@@ -91,8 +91,8 @@ Following are the key capabilities of this action:
|
|||||||
<td>Used to compute the number of replicas of '-baseline' and '-canary' variants of the workloads found in manifest files. For the specified percentage input, if (percentage * numberOfDesirerdReplicas)/100 is not a round number, the floor of this number is used while creating '-baseline' and '-canary'.<br/><br/>For example, if Deployment hello-world was found in the input manifest file with 'replicas: 4' and if 'strategy: canary' and 'percentage: 25' are given as inputs to the action, then the Deployments hello-world-baseline and hello-world-canary are created with 1 replica each. The '-baseline' variant is created with the same image and tag as the stable version (4 replica variant prior to deployment) while the '-canary' variant is created with the image and tag corresponding to the new changes being deployed</td>
|
<td>Used to compute the number of replicas of '-baseline' and '-canary' variants of the workloads found in manifest files. For the specified percentage input, if (percentage * numberOfDesirerdReplicas)/100 is not a round number, the floor of this number is used while creating '-baseline' and '-canary'.<br/><br/>For example, if Deployment hello-world was found in the input manifest file with 'replicas: 4' and if 'strategy: canary' and 'percentage: 25' are given as inputs to the action, then the Deployments hello-world-baseline and hello-world-canary are created with 1 replica each. The '-baseline' variant is created with the same image and tag as the stable version (4 replica variant prior to deployment) while the '-canary' variant is created with the image and tag corresponding to the new changes being deployed</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>baseline-and-canary-replicas </br></br> (Optional and relevant only if strategy is canary and traffic-split-method is smi)</td>
|
<td>baseline-and-canary-replicas </br></br> (Optional and relevant only if traffic-split-method is canary)</td>
|
||||||
<td>The number of baseline and canary replicas. Percentage traffic split is controlled in the service mesh plane, the actual number of replicas for canary and baseline variants could be controlled independently of the traffic split. For example, assume that the input Deployment manifest desired 30 replicas to be used for stable and that the following inputs were specified for the action </br></br><code> strategy: canary<br> trafficSplitMethod: smi<br> percentage: 20<br> baselineAndCanaryReplicas: 1</code></br></br> In this case, stable variant will receive 80% traffic while baseline and canary variants will receive 10% each (20% split equally between baseline and canary). However, instead of creating baseline and canary with 3 replicas each, the explicit count of baseline and canary replicas is honored. That is, only 1 replica each is created for baseline and canary variants.</td>
|
<td>The number of baseline and canary replicas. Percentage traffic split is controlled in the service mesh plane, the actual number of replicas for canary and baseline variants could be controlled independently of the traffic split. For example, assume that the input Deployment manifest desired 30 replicas to be used for stable and that the following inputs were specified for the action </br></br><code> strategy: canary<br> trafficSplitMethod: smi<br> percentage: 20<br> baselineAndCanaryReplicas: 1</code></br></br> In this case, stable variant will receive 80% traffic while baseline and canary variants will receive 10% each (20% split equally between baseline and canary). However, instead of creating baseline and canary with 3 replicas, the explicit count of baseline and canary replicas is honored. That is, only 1 replica each is created for baseline and canary variants.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>route-method </br></br>(Optional and relevant only if strategy is blue-green)</td>
|
<td>route-method </br></br>(Optional and relevant only if strategy is blue-green)</td>
|
||||||
@@ -117,10 +117,6 @@ Following are the key capabilities of this action:
|
|||||||
<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</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<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>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Usage Examples
|
## Usage Examples
|
||||||
@@ -128,7 +124,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@v4
|
- uses: Azure/k8s-deploy@v3.1
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
manifests: |
|
manifests: |
|
||||||
@@ -162,7 +158,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@v4
|
- uses: Azure/k8s-deploy@v3.1
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||||
@@ -181,7 +177,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@v4
|
- uses: Azure/k8s-deploy@v3.1
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||||
@@ -199,7 +195,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@v4
|
- uses: Azure/k8s-deploy@v3.1
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||||
@@ -220,7 +216,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@v4
|
- uses: Azure/k8s-deploy@v3.1
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }} '
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }} '
|
||||||
@@ -239,7 +235,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@v4
|
- uses: Azure/k8s-deploy@v3.1
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||||
@@ -259,7 +255,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@v4
|
- uses: Azure/k8s-deploy@v3.1
|
||||||
with:
|
with:
|
||||||
namespace: 'myapp'
|
namespace: 'myapp'
|
||||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||||
@@ -316,7 +312,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-deploy@v4
|
- uses: Azure/k8s-deploy@v3.1
|
||||||
with:
|
with:
|
||||||
action: deploy
|
action: deploy
|
||||||
manifests: |
|
manifests: |
|
||||||
@@ -362,7 +358,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-deploy@v4
|
- uses: Azure/k8s-deploy@v3.1
|
||||||
with:
|
with:
|
||||||
action: deploy
|
action: deploy
|
||||||
manifests: |
|
manifests: |
|
||||||
@@ -475,7 +471,3 @@ provided by the bot. You will only need to do this once across all repos using o
|
|||||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
k8s-deploy is an open source project that is [**not** covered by the Microsoft Azure support policy](https://support.microsoft.com/en-us/help/2941892/support-for-linux-and-open-source-technology-in-azure). [Please search open issues here](https://github.com/Azure/k8s-deploy/issues), and if your issue isn't already represented please [open a new one](https://github.com/Azure/k8s-deploy/issues/new/choose). The project maintainers will respond to the best of their abilities.
|
|
||||||
|
|||||||
+1
-5
@@ -6,7 +6,6 @@ inputs:
|
|||||||
namespace:
|
namespace:
|
||||||
description: 'Choose the target Kubernetes namespace. If the namespace is not provided, the commands will run in the default namespace.'
|
description: 'Choose the target Kubernetes namespace. If the namespace is not provided, the commands will run in the default namespace.'
|
||||||
required: false
|
required: false
|
||||||
default: default
|
|
||||||
manifests:
|
manifests:
|
||||||
description: 'Path to the manifest files which will be used for deployment.'
|
description: 'Path to the manifest files which will be used for deployment.'
|
||||||
required: true
|
required: true
|
||||||
@@ -42,7 +41,7 @@ inputs:
|
|||||||
baseline-and-canary-replicas:
|
baseline-and-canary-replicas:
|
||||||
description: 'Baseline and canary replicas count. Valid value between 0 to 100 (inclusive)'
|
description: 'Baseline and canary replicas count. Valid value between 0 to 100 (inclusive)'
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: 0
|
||||||
percentage:
|
percentage:
|
||||||
description: 'Percentage of traffic redirect to canary deployment'
|
description: 'Percentage of traffic redirect to canary deployment'
|
||||||
required: false
|
required: false
|
||||||
@@ -73,9 +72,6 @@ inputs:
|
|||||||
name:
|
name:
|
||||||
description: 'Resource group name - Only required if using private cluster'
|
description: 'Resource group name - Only required if using private cluster'
|
||||||
required: false
|
required: false
|
||||||
skip-tls-verify:
|
|
||||||
description: True if the insecure-skip-tls-verify option should be used. Input should be 'true' or 'false'.
|
|
||||||
default: false
|
|
||||||
|
|
||||||
branding:
|
branding:
|
||||||
color: 'green'
|
color: 'green'
|
||||||
|
|||||||
+1
-2
@@ -6,6 +6,5 @@ module.exports = {
|
|||||||
transform: {
|
transform: {
|
||||||
'^.+\\.ts$': 'ts-jest'
|
'^.+\\.ts$': 'ts-jest'
|
||||||
},
|
},
|
||||||
verbose: true,
|
verbose: true
|
||||||
testTimeout: 9000
|
|
||||||
}
|
}
|
||||||
|
|||||||
-24092
File diff suppressed because one or more lines are too long
Generated
+12
-39
@@ -9,7 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.0",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/exec": "^1.0.0",
|
"@actions/exec": "^1.0.0",
|
||||||
"@actions/io": "^1.0.0",
|
"@actions/io": "^1.0.0",
|
||||||
"@actions/tool-cache": "1.1.2",
|
"@actions/tool-cache": "1.1.2",
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
"@types/jest": "^26.0.0",
|
"@types/jest": "^26.0.0",
|
||||||
"@types/js-yaml": "^3.12.7",
|
"@types/js-yaml": "^3.12.7",
|
||||||
"@types/node": "^12.20.41",
|
"@types/node": "^12.20.41",
|
||||||
"@vercel/ncc": "^0.36.1",
|
|
||||||
"jest": "^26.0.0",
|
"jest": "^26.0.0",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"ts-jest": "^26.0.0",
|
"ts-jest": "^26.0.0",
|
||||||
@@ -30,20 +29,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
"version": "1.10.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.0.tgz",
|
||||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
"integrity": "sha512-5pbM693Ih59ZdUhgk+fts+bUWTnIdHV3kwOSr+QIoFHMLg7Gzhwm0cifDY/AG68ekEJAkHnQVpcy4f6GjmzBCA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/http-client": "^2.0.1",
|
"@actions/http-client": "^2.0.1"
|
||||||
"uuid": "^8.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@actions/core/node_modules/uuid": {
|
|
||||||
"version": "8.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
|
||||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
|
||||||
"bin": {
|
|
||||||
"uuid": "dist/bin/uuid"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/exec": {
|
"node_modules/@actions/exec": {
|
||||||
@@ -1183,15 +1173,6 @@
|
|||||||
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
|
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@vercel/ncc": {
|
|
||||||
"version": "0.36.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.1.tgz",
|
|
||||||
"integrity": "sha512-S4cL7Taa9yb5qbv+6wLgiKVZ03Qfkc4jGRuiUQMQ8HGBD5pcNRnHeYM33zBvJE4/zJGjJJ8GScB+WmTsn9mORw==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"ncc": "dist/ncc/cli.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/abab": {
|
"node_modules/abab": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||||
@@ -6426,19 +6407,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": {
|
"@actions/core": {
|
||||||
"version": "1.10.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.0.tgz",
|
||||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
"integrity": "sha512-5pbM693Ih59ZdUhgk+fts+bUWTnIdHV3kwOSr+QIoFHMLg7Gzhwm0cifDY/AG68ekEJAkHnQVpcy4f6GjmzBCA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@actions/http-client": "^2.0.1",
|
"@actions/http-client": "^2.0.1"
|
||||||
"uuid": "^8.3.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"uuid": {
|
|
||||||
"version": "8.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
|
||||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@actions/exec": {
|
"@actions/exec": {
|
||||||
@@ -7391,9 +7364,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@vercel/ncc": {
|
"@vercel/ncc": {
|
||||||
"version": "0.36.1",
|
"version": "0.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.34.0.tgz",
|
||||||
"integrity": "sha512-S4cL7Taa9yb5qbv+6wLgiKVZ03Qfkc4jGRuiUQMQ8HGBD5pcNRnHeYM33zBvJE4/zJGjJJ8GScB+WmTsn9mORw==",
|
"integrity": "sha512-G9h5ZLBJ/V57Ou9vz5hI8pda/YQX5HQszCs3AmIus3XzsmRn/0Ptic5otD3xVST8QLKk7AMk7AqpsyQGN7MZ9A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"abab": {
|
"abab": {
|
||||||
|
|||||||
+2
-3
@@ -4,14 +4,14 @@
|
|||||||
"author": "Deepak Sattiraju",
|
"author": "Deepak Sattiraju",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npx ncc build src/run.ts -o lib",
|
"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.10.0",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/exec": "^1.0.0",
|
"@actions/exec": "^1.0.0",
|
||||||
"@actions/io": "^1.0.0",
|
"@actions/io": "^1.0.0",
|
||||||
"@actions/tool-cache": "1.1.2",
|
"@actions/tool-cache": "1.1.2",
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
"@types/jest": "^26.0.0",
|
"@types/jest": "^26.0.0",
|
||||||
"@types/js-yaml": "^3.12.7",
|
"@types/js-yaml": "^3.12.7",
|
||||||
"@types/node": "^12.20.41",
|
"@types/node": "^12.20.41",
|
||||||
"@vercel/ncc": "^0.36.1",
|
|
||||||
"jest": "^26.0.0",
|
"jest": "^26.0.0",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"ts-jest": "^26.0.0",
|
"ts-jest": "^26.0.0",
|
||||||
|
|||||||
@@ -56,9 +56,7 @@ 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()
|
||||||
|
|||||||
+8
-66
@@ -1,12 +1,11 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
|
import * as deploy from './deploy'
|
||||||
import * as canaryDeploymentHelper from '../strategyHelpers/canary/canaryHelper'
|
import * as canaryDeploymentHelper from '../strategyHelpers/canary/canaryHelper'
|
||||||
import * as SMICanaryDeploymentHelper from '../strategyHelpers/canary/smiCanaryHelper'
|
import * as SMICanaryDeploymentHelper from '../strategyHelpers/canary/smiCanaryHelper'
|
||||||
import * as PodCanaryHelper from '../strategyHelpers/canary/podCanaryHelper'
|
|
||||||
import {
|
import {
|
||||||
getResources,
|
getResources,
|
||||||
updateManifestFiles
|
updateManifestFiles
|
||||||
} from '../utilities/manifestUpdateUtils'
|
} from '../utilities/manifestUpdateUtils'
|
||||||
import {annotateAndLabelResources} from '../strategyHelpers/deploymentHelper'
|
|
||||||
import * as models from '../types/kubernetesTypes'
|
import * as models from '../types/kubernetesTypes'
|
||||||
import * as KubernetesManifestUtility from '../utilities/manifestStabilityUtils'
|
import * as KubernetesManifestUtility from '../utilities/manifestStabilityUtils'
|
||||||
import {
|
import {
|
||||||
@@ -16,7 +15,6 @@ import {
|
|||||||
} from '../strategyHelpers/blueGreen/blueGreenHelper'
|
} from '../strategyHelpers/blueGreen/blueGreenHelper'
|
||||||
|
|
||||||
import {BlueGreenManifests} from '../types/blueGreenTypes'
|
import {BlueGreenManifests} from '../types/blueGreenTypes'
|
||||||
import {DeployResult} from '../types/deployResult'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
promoteBlueGreenIngress,
|
promoteBlueGreenIngress,
|
||||||
@@ -59,13 +57,9 @@ export async function promote(
|
|||||||
async function promoteCanary(kubectl: Kubectl, manifests: string[]) {
|
async function promoteCanary(kubectl: Kubectl, manifests: string[]) {
|
||||||
let includeServices = false
|
let includeServices = false
|
||||||
|
|
||||||
const manifestFilesForDeployment: string[] = updateManifestFiles(manifests)
|
|
||||||
|
|
||||||
const trafficSplitMethod = parseTrafficSplitMethod(
|
const trafficSplitMethod = parseTrafficSplitMethod(
|
||||||
core.getInput('traffic-split-method', {required: true})
|
core.getInput('traffic-split-method', {required: true})
|
||||||
)
|
)
|
||||||
let promoteResult: DeployResult
|
|
||||||
let filesToAnnotate: string[]
|
|
||||||
if (trafficSplitMethod == TrafficSplitMethod.SMI) {
|
if (trafficSplitMethod == TrafficSplitMethod.SMI) {
|
||||||
includeServices = true
|
includeServices = true
|
||||||
|
|
||||||
@@ -78,38 +72,19 @@ async function promoteCanary(kubectl: Kubectl, manifests: string[]) {
|
|||||||
)
|
)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
core.startGroup(
|
core.startGroup('Deploying input manifests with SMI canary strategy')
|
||||||
'Deploying input manifests with SMI canary strategy from promote'
|
await deploy.deploy(kubectl, manifests, DeploymentStrategy.CANARY)
|
||||||
)
|
|
||||||
|
|
||||||
promoteResult = await SMICanaryDeploymentHelper.deploySMICanary(
|
|
||||||
manifestFilesForDeployment,
|
|
||||||
kubectl,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
core.startGroup('Redirecting traffic to stable deployment')
|
core.startGroup('Redirecting traffic to stable deployment')
|
||||||
const stableRedirectManifests =
|
await SMICanaryDeploymentHelper.redirectTrafficToStableDeployment(
|
||||||
await SMICanaryDeploymentHelper.redirectTrafficToStableDeployment(
|
kubectl,
|
||||||
kubectl,
|
manifests
|
||||||
manifests
|
|
||||||
)
|
|
||||||
|
|
||||||
filesToAnnotate = promoteResult.manifestFiles.concat(
|
|
||||||
stableRedirectManifests
|
|
||||||
)
|
)
|
||||||
|
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
} else {
|
} else {
|
||||||
core.startGroup('Deploying input manifests from promote')
|
core.startGroup('Deploying input manifests')
|
||||||
promoteResult = await PodCanaryHelper.deployPodCanary(
|
await deploy.deploy(kubectl, manifests, DeploymentStrategy.CANARY)
|
||||||
manifestFilesForDeployment,
|
|
||||||
kubectl,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
filesToAnnotate = promoteResult.manifestFiles
|
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,23 +101,6 @@ async function promoteCanary(kubectl: Kubectl, manifests: string[]) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
// annotate 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(
|
|
||||||
filesToAnnotate,
|
|
||||||
models.DEPLOYMENT_TYPES.concat([
|
|
||||||
models.DiscoveryAndLoadBalancerResource.SERVICE
|
|
||||||
])
|
|
||||||
)
|
|
||||||
await annotateAndLabelResources(filesToAnnotate, kubectl, resources, allPods)
|
|
||||||
core.endGroup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function promoteBlueGreen(kubectl: Kubectl, manifests: string[]) {
|
async function promoteBlueGreen(kubectl: Kubectl, manifests: string[]) {
|
||||||
@@ -216,20 +174,4 @@ async function promoteBlueGreen(kubectl: Kubectl, manifests: string[]) {
|
|||||||
await deleteGreenObjects(kubectl, manifestObjects.deploymentEntityList)
|
await deleteGreenObjects(kubectl, manifestObjects.deploymentEntityList)
|
||||||
}
|
}
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
// annotate 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(
|
|
||||||
deployedManifestFiles,
|
|
||||||
kubectl,
|
|
||||||
resources,
|
|
||||||
allPods
|
|
||||||
)
|
|
||||||
core.endGroup()
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-7
@@ -5,7 +5,7 @@ import {promote} from './actions/promote'
|
|||||||
import {reject} from './actions/reject'
|
import {reject} from './actions/reject'
|
||||||
import {Action, parseAction} from './types/action'
|
import {Action, parseAction} from './types/action'
|
||||||
import {parseDeploymentStrategy} from './types/deploymentStrategy'
|
import {parseDeploymentStrategy} from './types/deploymentStrategy'
|
||||||
import {getFilesFromDirectoriesAndURLs} from './utilities/fileUtils'
|
import {getFilesFromDirectories} from './utilities/fileUtils'
|
||||||
import {PrivateKubectl} from './types/privatekubectl'
|
import {PrivateKubectl} from './types/privatekubectl'
|
||||||
|
|
||||||
export async function run() {
|
export async function run() {
|
||||||
@@ -26,26 +26,23 @@ 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 = getFilesFromDirectories(manifestFilePaths)
|
||||||
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 =
|
||||||
core.getInput('private-cluster').toLowerCase() === 'true'
|
core.getInput('private-cluster').toLowerCase() === 'true'
|
||||||
const resourceGroup = core.getInput('resource-group') || ''
|
const resourceGroup = core.getInput('resource-group') || ''
|
||||||
const resourceName = core.getInput('name') || ''
|
const resourceName = core.getInput('name') || ''
|
||||||
const skipTlsVerify = core.getBooleanInput('skip-tls-verify')
|
|
||||||
|
|
||||||
const kubectl = isPrivateCluster
|
const kubectl = isPrivateCluster
|
||||||
? new PrivateKubectl(
|
? new PrivateKubectl(
|
||||||
kubectlPath,
|
kubectlPath,
|
||||||
namespace,
|
namespace,
|
||||||
skipTlsVerify,
|
true,
|
||||||
resourceGroup,
|
resourceGroup,
|
||||||
resourceName
|
resourceName
|
||||||
)
|
)
|
||||||
: new Kubectl(kubectlPath, namespace, skipTlsVerify)
|
: new Kubectl(kubectlPath, namespace, true)
|
||||||
|
|
||||||
// run action
|
// run action
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ 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
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -67,25 +66,31 @@ 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()
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
yaml.safeLoadAll(fileContents, (inputObject) => {
|
yaml.safeLoadAll(fileContents, (inputObject) => {
|
||||||
if (!!inputObject) {
|
if (!!inputObject) {
|
||||||
const kind = inputObject.kind
|
const kind = inputObject.kind
|
||||||
|
const name = inputObject.metadata.name
|
||||||
|
|
||||||
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)) {
|
||||||
|
routedServiceEntityList.push(inputObject)
|
||||||
|
serviceNameMap.set(
|
||||||
|
name,
|
||||||
|
getBlueGreenResourceName(name, GREEN_SUFFIX)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
unroutedServiceEntityList.push(inputObject)
|
||||||
|
}
|
||||||
} else if (isIngressEntity(kind)) {
|
} else if (isIngressEntity(kind)) {
|
||||||
ingressEntityList.push(inputObject)
|
ingressEntityList.push(inputObject)
|
||||||
} else {
|
} else {
|
||||||
@@ -95,16 +100,6 @@ export function getManifestObjects(filePaths: string[]): BlueGreenManifests {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
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 {
|
||||||
serviceEntityList: routedServiceEntityList,
|
serviceEntityList: routedServiceEntityList,
|
||||||
serviceNameMap: serviceNameMap,
|
serviceNameMap: serviceNameMap,
|
||||||
@@ -239,10 +234,9 @@ 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, false, namespace)
|
const result = await kubectl.getResource(kind, name)
|
||||||
if (result == null || !!result.stderr) {
|
if (result == null || !!result.stderr) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ describe('deploy tests', () => {
|
|||||||
RouteStrategy.SMI
|
RouteStrategy.SMI
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(smiResult.objects.length).toBe(6)
|
expect(smiResult.objects.length).toBe(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('correctly deploys blue/green ingress', async () => {
|
test('correctly deploys blue/green ingress', async () => {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
import {setupSMI} from './smiBlueGreenHelper'
|
import {setupSMI} from './smiBlueGreenHelper'
|
||||||
|
|
||||||
import {routeBlueGreenForDeploy} from './route'
|
import {routeBlueGreenForDeploy} from './route'
|
||||||
import {DeployResult} from '../../types/deployResult'
|
|
||||||
|
|
||||||
export async function deployBlueGreen(
|
export async function deployBlueGreen(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
@@ -36,17 +35,9 @@ export async function deployBlueGreen(
|
|||||||
})()
|
})()
|
||||||
|
|
||||||
core.startGroup('Routing blue green')
|
core.startGroup('Routing blue green')
|
||||||
const routeDeployment = await routeBlueGreenForDeploy(
|
await routeBlueGreenForDeploy(kubectl, files, routeStrategy)
|
||||||
kubectl,
|
|
||||||
files,
|
|
||||||
routeStrategy
|
|
||||||
)
|
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
blueGreenDeployment.objects.push(...routeDeployment.objects)
|
|
||||||
blueGreenDeployment.deployResult.manifestFiles.push(
|
|
||||||
...routeDeployment.deployResult.manifestFiles
|
|
||||||
)
|
|
||||||
return blueGreenDeployment
|
return blueGreenDeployment
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,16 +56,10 @@ export async function deployBlueGreenSMI(
|
|||||||
manifestObjects.unroutedServiceEntityList
|
manifestObjects.unroutedServiceEntityList
|
||||||
)
|
)
|
||||||
|
|
||||||
const otherObjDeployment: DeployResult = await deployObjects(
|
await deployObjects(kubectl, newObjectsList)
|
||||||
kubectl,
|
|
||||||
newObjectsList
|
|
||||||
)
|
|
||||||
|
|
||||||
// make extraservices and trafficsplit
|
// make extraservices and trafficsplit
|
||||||
const smiAndSvcDeployment = await setupSMI(
|
await setupSMI(kubectl, manifestObjects.serviceEntityList)
|
||||||
kubectl,
|
|
||||||
manifestObjects.serviceEntityList
|
|
||||||
)
|
|
||||||
|
|
||||||
// create new deloyments
|
// create new deloyments
|
||||||
const blueGreenDeployment: BlueGreenDeployment = await deployWithLabel(
|
const blueGreenDeployment: BlueGreenDeployment = await deployWithLabel(
|
||||||
@@ -82,18 +67,10 @@ export async function deployBlueGreenSMI(
|
|||||||
manifestObjects.deploymentEntityList,
|
manifestObjects.deploymentEntityList,
|
||||||
GREEN_LABEL_VALUE
|
GREEN_LABEL_VALUE
|
||||||
)
|
)
|
||||||
|
return {
|
||||||
blueGreenDeployment.objects.push(...newObjectsList)
|
deployResult: blueGreenDeployment.deployResult,
|
||||||
blueGreenDeployment.objects.push(...smiAndSvcDeployment.objects)
|
objects: [].concat(blueGreenDeployment.objects, newObjectsList)
|
||||||
|
}
|
||||||
blueGreenDeployment.deployResult.manifestFiles.push(
|
|
||||||
...otherObjDeployment.manifestFiles
|
|
||||||
)
|
|
||||||
blueGreenDeployment.deployResult.manifestFiles.push(
|
|
||||||
...smiAndSvcDeployment.deployResult.manifestFiles
|
|
||||||
)
|
|
||||||
|
|
||||||
return blueGreenDeployment
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deployBlueGreenIngress(
|
export async function deployBlueGreenIngress(
|
||||||
|
|||||||
@@ -97,8 +97,7 @@ 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 =
|
||||||
|
|||||||
@@ -61,6 +61,6 @@ describe('reject tests', () => {
|
|||||||
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
|
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
|
||||||
.mockImplementation(() => Promise.resolve('v1alpha3'))
|
.mockImplementation(() => Promise.resolve('v1alpha3'))
|
||||||
const rejectResult = await rejectBlueGreenSMI(kubectl, testObjects)
|
const rejectResult = await rejectBlueGreenSMI(kubectl, testObjects)
|
||||||
expect(rejectResult.deleteResult).toHaveLength(2)
|
expect(rejectResult.deleteResult).toHaveLength(4)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ 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 =
|
||||||
|
|||||||
@@ -193,8 +193,11 @@ describe('SMI Helper tests', () => {
|
|||||||
|
|
||||||
test('cleanupSMI test', async () => {
|
test('cleanupSMI test', async () => {
|
||||||
const deleteObjects = await cleanupSMI(kc, testObjects.serviceEntityList)
|
const deleteObjects = await cleanupSMI(kc, testObjects.serviceEntityList)
|
||||||
expect(deleteObjects).toHaveLength(1)
|
expect(deleteObjects).toHaveLength(3)
|
||||||
expect(deleteObjects[0].name).toBe('nginx-service-green')
|
expect(deleteObjects[0].name).toBe('nginx-service-trafficsplit')
|
||||||
expect(deleteObjects[0].kind).toBe('Service')
|
expect(deleteObjects[1].name).toBe('nginx-service-green')
|
||||||
|
expect(deleteObjects[1].kind).toBe('Service')
|
||||||
|
expect(deleteObjects[2].name).toBe('nginx-service-stable')
|
||||||
|
expect(deleteObjects[2].kind).toBe('Service')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -142,8 +142,7 @@ 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)}`
|
||||||
@@ -179,13 +178,28 @@ export async function cleanupSMI(
|
|||||||
const deleteList: K8sDeleteObject[] = []
|
const deleteList: K8sDeleteObject[] = []
|
||||||
|
|
||||||
serviceEntityList.forEach((serviceObject) => {
|
serviceEntityList.forEach((serviceObject) => {
|
||||||
|
deleteList.push({
|
||||||
|
name: getBlueGreenResourceName(
|
||||||
|
serviceObject.metadata.name,
|
||||||
|
TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX
|
||||||
|
),
|
||||||
|
kind: TRAFFIC_SPLIT_OBJECT
|
||||||
|
})
|
||||||
|
|
||||||
deleteList.push({
|
deleteList.push({
|
||||||
name: getBlueGreenResourceName(
|
name: getBlueGreenResourceName(
|
||||||
serviceObject.metadata.name,
|
serviceObject.metadata.name,
|
||||||
GREEN_SUFFIX
|
GREEN_SUFFIX
|
||||||
),
|
),
|
||||||
kind: serviceObject.kind,
|
kind: serviceObject.kind
|
||||||
namespace: serviceObject?.metadata?.namespace
|
})
|
||||||
|
|
||||||
|
deleteList.push({
|
||||||
|
name: getBlueGreenResourceName(
|
||||||
|
serviceObject.metadata.name,
|
||||||
|
STABLE_SUFFIX
|
||||||
|
),
|
||||||
|
kind: serviceObject.kind
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {Kubectl} from '../../types/kubectl'
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as yaml from 'js-yaml'
|
import * as yaml from 'js-yaml'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {ExecOutput} from '@actions/exec'
|
|
||||||
import {
|
import {
|
||||||
isDeploymentEntity,
|
isDeploymentEntity,
|
||||||
isServiceEntity,
|
isServiceEntity,
|
||||||
@@ -29,17 +28,12 @@ export async function deleteCanaryDeployment(
|
|||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
manifestFilePaths: string[],
|
manifestFilePaths: string[],
|
||||||
includeServices: boolean
|
includeServices: boolean
|
||||||
): Promise<string[]> {
|
) {
|
||||||
if (manifestFilePaths == null || manifestFilePaths.length == 0) {
|
if (manifestFilePaths == null || manifestFilePaths.length == 0) {
|
||||||
throw new Error('Manifest files for deleting canary deployment not found')
|
throw new Error('Manifest file not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedFiles = await cleanUpCanary(
|
await cleanUpCanary(kubectl, manifestFilePaths, includeServices)
|
||||||
kubectl,
|
|
||||||
manifestFilePaths,
|
|
||||||
includeServices
|
|
||||||
)
|
|
||||||
return deletedFiles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markResourceAsStable(inputObject: any): object {
|
export function markResourceAsStable(inputObject: any): object {
|
||||||
@@ -60,7 +54,7 @@ export function isResourceMarkedAsStable(inputObject: any): boolean {
|
|||||||
|
|
||||||
export function getStableResource(inputObject: any): object {
|
export function getStableResource(inputObject: any): object {
|
||||||
const replicaCount = specContainsReplicas(inputObject.kind)
|
const replicaCount = specContainsReplicas(inputObject.kind)
|
||||||
? inputObject.spec.replicas
|
? inputObject.metadata.replicas
|
||||||
: 0
|
: 0
|
||||||
|
|
||||||
return getNewCanaryObject(inputObject, replicaCount, STABLE_LABEL_VALUE)
|
return getNewCanaryObject(inputObject, replicaCount, STABLE_LABEL_VALUE)
|
||||||
@@ -85,12 +79,7 @@ export async function fetchResource(
|
|||||||
kind: string,
|
kind: string,
|
||||||
name: string
|
name: string
|
||||||
) {
|
) {
|
||||||
let result: ExecOutput
|
const result = await kubectl.getResource(kind, name)
|
||||||
try {
|
|
||||||
result = await kubectl.getResource(kind, name)
|
|
||||||
} catch (e) {
|
|
||||||
core.debug(`detected error while fetching resources: ${e}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result || result?.stderr) {
|
if (!result || result?.stderr) {
|
||||||
return null
|
return null
|
||||||
@@ -104,7 +93,7 @@ export async function fetchResource(
|
|||||||
return resource
|
return resource
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
core.debug(
|
core.debug(
|
||||||
`Exception occurred while parsing ${resource} in JSON object: ${ex}`
|
`Exception occurred while Parsing ${resource} in JSON object: ${ex}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,26 +111,6 @@ export function getStableResourceName(name: string) {
|
|||||||
return name + STABLE_SUFFIX
|
return name + STABLE_SUFFIX
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBaselineDeploymentFromStableDeployment(
|
|
||||||
inputObject: any,
|
|
||||||
replicaCount: number
|
|
||||||
): object {
|
|
||||||
// TODO: REFACTOR TO MAKE EVERYTHING TYPE SAFE
|
|
||||||
const oldName = inputObject.metadata.name
|
|
||||||
const newName =
|
|
||||||
oldName.substring(0, oldName.length - STABLE_SUFFIX.length) +
|
|
||||||
BASELINE_SUFFIX
|
|
||||||
|
|
||||||
const newObject = getNewCanaryObject(
|
|
||||||
inputObject,
|
|
||||||
replicaCount,
|
|
||||||
BASELINE_LABEL_VALUE
|
|
||||||
) as any
|
|
||||||
newObject.metadata.name = newName
|
|
||||||
|
|
||||||
return newObject
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNewCanaryObject(
|
function getNewCanaryObject(
|
||||||
inputObject: any,
|
inputObject: any,
|
||||||
replicas: number,
|
replicas: number,
|
||||||
@@ -194,22 +163,16 @@ async function cleanUpCanary(
|
|||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
files: string[],
|
files: string[],
|
||||||
includeServices: boolean
|
includeServices: boolean
|
||||||
): Promise<string[]> {
|
) {
|
||||||
const deleteObject = async function (
|
const deleteObject = async function (kind, name) {
|
||||||
kind: string,
|
|
||||||
name: string,
|
|
||||||
namespace: string | undefined
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const result = await kubectl.delete([kind, name], namespace)
|
const result = await kubectl.delete([kind, name])
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedFiles: string[] = []
|
|
||||||
|
|
||||||
for (const filePath of files) {
|
for (const filePath of files) {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
|
|
||||||
@@ -217,21 +180,17 @@ async function cleanUpCanary(
|
|||||||
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)
|
|
||||||
const canaryObjectName = getCanaryResourceName(name)
|
const canaryObjectName = getCanaryResourceName(name)
|
||||||
const baselineObjectName = getBaselineResourceName(name)
|
const baselineObjectName = getBaselineResourceName(name)
|
||||||
|
|
||||||
await deleteObject(kind, canaryObjectName, namespace)
|
await deleteObject(kind, canaryObjectName)
|
||||||
await deleteObject(kind, baselineObjectName, namespace)
|
await deleteObject(kind, baselineObjectName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return deletedFiles
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,10 @@ import * as fileHelper from '../../utilities/fileUtils'
|
|||||||
import * as canaryDeploymentHelper from './canaryHelper'
|
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'
|
|
||||||
|
|
||||||
export async function deployPodCanary(
|
export async function deployPodCanary(filePaths: string[], kubectl: Kubectl) {
|
||||||
filePaths: string[],
|
|
||||||
kubectl: Kubectl,
|
|
||||||
onlyDeployStable: boolean = false
|
|
||||||
): Promise<DeployResult> {
|
|
||||||
const newObjectsList = []
|
const newObjectsList = []
|
||||||
const percentage = parseInt(core.getInput('percentage', {required: true}))
|
const percentage = parseInt(core.getInput('percentage'))
|
||||||
|
|
||||||
if (percentage < 0 || percentage > 100)
|
if (percentage < 0 || percentage > 100)
|
||||||
throw Error('Percentage must be between 0 and 100')
|
throw Error('Percentage must be between 0 and 100')
|
||||||
@@ -27,7 +22,7 @@ export async function deployPodCanary(
|
|||||||
const name = inputObject.metadata.name
|
const name = inputObject.metadata.name
|
||||||
const kind = inputObject.kind
|
const kind = inputObject.kind
|
||||||
|
|
||||||
if (!onlyDeployStable && isDeploymentEntity(kind)) {
|
if (isDeploymentEntity(kind)) {
|
||||||
core.debug('Calculating replica count for canary')
|
core.debug('Calculating replica count for canary')
|
||||||
const canaryReplicaCount = calculateReplicaCountForCanary(
|
const canaryReplicaCount = calculateReplicaCountForCanary(
|
||||||
inputObject,
|
inputObject,
|
||||||
@@ -35,22 +30,37 @@ export async function deployPodCanary(
|
|||||||
)
|
)
|
||||||
core.debug('Replica count is ' + canaryReplicaCount)
|
core.debug('Replica count is ' + canaryReplicaCount)
|
||||||
|
|
||||||
const newCanaryObject = canaryDeploymentHelper.getNewCanaryResource(
|
// Get stable object
|
||||||
inputObject,
|
core.debug('Querying stable object')
|
||||||
canaryReplicaCount
|
|
||||||
)
|
|
||||||
newObjectsList.push(newCanaryObject)
|
|
||||||
|
|
||||||
// if there's already a stable object, deploy baseline as well
|
|
||||||
const stableObject = await canaryDeploymentHelper.fetchResource(
|
const stableObject = await canaryDeploymentHelper.fetchResource(
|
||||||
kubectl,
|
kubectl,
|
||||||
kind,
|
kind,
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
if (stableObject) {
|
|
||||||
|
if (!stableObject) {
|
||||||
|
core.debug('Stable object not found. Creating canary object')
|
||||||
|
const newCanaryObject =
|
||||||
|
canaryDeploymentHelper.getNewCanaryResource(
|
||||||
|
inputObject,
|
||||||
|
canaryReplicaCount
|
||||||
|
)
|
||||||
|
newObjectsList.push(newCanaryObject)
|
||||||
|
} else {
|
||||||
core.debug(
|
core.debug(
|
||||||
`Stable object found for ${kind} ${name}. Creating baseline objects`
|
'Creating canary and baseline objects. Stable object found: ' +
|
||||||
|
JSON.stringify(stableObject)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const newCanaryObject =
|
||||||
|
canaryDeploymentHelper.getNewCanaryResource(
|
||||||
|
inputObject,
|
||||||
|
canaryReplicaCount
|
||||||
|
)
|
||||||
|
core.debug(
|
||||||
|
'New canary object: ' + JSON.stringify(newCanaryObject)
|
||||||
|
)
|
||||||
|
|
||||||
const newBaselineObject =
|
const newBaselineObject =
|
||||||
canaryDeploymentHelper.getNewBaselineResource(
|
canaryDeploymentHelper.getNewBaselineResource(
|
||||||
stableObject,
|
stableObject,
|
||||||
@@ -59,10 +69,12 @@ export async function deployPodCanary(
|
|||||||
core.debug(
|
core.debug(
|
||||||
'New baseline object: ' + JSON.stringify(newBaselineObject)
|
'New baseline object: ' + JSON.stringify(newBaselineObject)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
newObjectsList.push(newCanaryObject)
|
||||||
newObjectsList.push(newBaselineObject)
|
newObjectsList.push(newBaselineObject)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// deploy non deployment entity or regular deployments for promote as they are
|
// update non deployment entity as it is
|
||||||
newObjectsList.push(inputObject)
|
newObjectsList.push(inputObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,14 +84,11 @@ export async function deployPodCanary(
|
|||||||
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList)
|
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList)
|
||||||
const forceDeployment = core.getInput('force').toLowerCase() === 'true'
|
const forceDeployment = core.getInput('force').toLowerCase() === 'true'
|
||||||
|
|
||||||
const execResult = await kubectl.apply(manifestFiles, forceDeployment)
|
const result = await kubectl.apply(manifestFiles, forceDeployment)
|
||||||
return {execResult, manifestFiles}
|
return {result, newFilePaths: manifestFiles}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateReplicaCountForCanary(
|
function calculateReplicaCountForCanary(inputObject: any, percentage: number) {
|
||||||
inputObject: any,
|
|
||||||
percentage: number
|
|
||||||
) {
|
|
||||||
const inputReplicaCount = getReplicaCount(inputObject)
|
const inputReplicaCount = getReplicaCount(inputObject)
|
||||||
return Math.max(1, Math.round((inputReplicaCount * percentage) / 100))
|
return Math.round((inputReplicaCount * percentage) / 100)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,109 +6,84 @@ import * as yaml from 'js-yaml'
|
|||||||
import * as fileHelper from '../../utilities/fileUtils'
|
import * as fileHelper from '../../utilities/fileUtils'
|
||||||
import * as kubectlUtils from '../../utilities/trafficSplitUtils'
|
import * as kubectlUtils from '../../utilities/trafficSplitUtils'
|
||||||
import * as canaryDeploymentHelper from './canaryHelper'
|
import * as canaryDeploymentHelper from './canaryHelper'
|
||||||
import * as podCanaryHelper from './podCanaryHelper'
|
|
||||||
import {isDeploymentEntity, isServiceEntity} from '../../types/kubernetesTypes'
|
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'
|
|
||||||
|
|
||||||
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'
|
||||||
|
|
||||||
export async function deploySMICanary(
|
export async function deploySMICanary(filePaths: string[], kubectl: Kubectl) {
|
||||||
filePaths: string[],
|
const canaryReplicaCount = parseInt(
|
||||||
kubectl: Kubectl,
|
core.getInput('baseline-and-canary-replicas')
|
||||||
onlyDeployStable: boolean = false
|
)
|
||||||
): Promise<DeployResult> {
|
if (canaryReplicaCount < 0 || canaryReplicaCount > 100)
|
||||||
const canaryReplicasInput = core.getInput('baseline-and-canary-replicas')
|
|
||||||
let canaryReplicaCount
|
|
||||||
let calculateReplicas = true
|
|
||||||
if (canaryReplicasInput !== '') {
|
|
||||||
canaryReplicaCount = parseInt(canaryReplicasInput)
|
|
||||||
calculateReplicas = false
|
|
||||||
core.debug(
|
|
||||||
`read replica count ${canaryReplicaCount} from input: ${canaryReplicasInput}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canaryReplicaCount < 0 && canaryReplicaCount > 100)
|
|
||||||
throw Error('Baseline-and-canary-replicas must be between 0 and 100')
|
throw Error('Baseline-and-canary-replicas must be between 0 and 100')
|
||||||
|
|
||||||
const newObjectsList = []
|
const newObjectsList = []
|
||||||
for await (const filePath of filePaths) {
|
filePaths.forEach((filePath: string) => {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
const inputObjects = yaml.safeLoadAll(fileContents)
|
yaml.safeLoadAll(fileContents, (inputObject) => {
|
||||||
for (const inputObject of inputObjects) {
|
|
||||||
const name = inputObject.metadata.name
|
const name = inputObject.metadata.name
|
||||||
const kind = inputObject.kind
|
const kind = inputObject.kind
|
||||||
|
|
||||||
if (!onlyDeployStable && isDeploymentEntity(kind)) {
|
if (isDeploymentEntity(kind)) {
|
||||||
if (calculateReplicas) {
|
const stableObject = canaryDeploymentHelper.fetchResource(
|
||||||
// calculate for each object
|
|
||||||
const percentage = parseInt(
|
|
||||||
core.getInput('percentage', {required: true})
|
|
||||||
)
|
|
||||||
canaryReplicaCount =
|
|
||||||
podCanaryHelper.calculateReplicaCountForCanary(
|
|
||||||
inputObject,
|
|
||||||
percentage
|
|
||||||
)
|
|
||||||
core.debug(`calculated replica count ${canaryReplicaCount}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
core.debug('Creating canary object')
|
|
||||||
const newCanaryObject = canaryDeploymentHelper.getNewCanaryResource(
|
|
||||||
inputObject,
|
|
||||||
canaryReplicaCount
|
|
||||||
)
|
|
||||||
newObjectsList.push(newCanaryObject)
|
|
||||||
|
|
||||||
const stableObject = await canaryDeploymentHelper.fetchResource(
|
|
||||||
kubectl,
|
kubectl,
|
||||||
kind,
|
kind,
|
||||||
canaryDeploymentHelper.getStableResourceName(name)
|
name
|
||||||
)
|
)
|
||||||
if (stableObject) {
|
|
||||||
|
if (!stableObject) {
|
||||||
core.debug(
|
core.debug(
|
||||||
`Stable object found for ${kind} ${name}. Creating baseline objects`
|
'Stable object not found. Creating only canary object'
|
||||||
)
|
)
|
||||||
|
const newCanaryObject =
|
||||||
|
canaryDeploymentHelper.getNewCanaryResource(
|
||||||
|
inputObject,
|
||||||
|
canaryReplicaCount
|
||||||
|
)
|
||||||
|
newObjectsList.push(newCanaryObject)
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
!canaryDeploymentHelper.isResourceMarkedAsStable(stableObject)
|
||||||
|
) {
|
||||||
|
throw Error(`StableSpecSelectorNotExist : ${name}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
core.debug(
|
||||||
|
'Stable object found. Creating canary and baseline objects'
|
||||||
|
)
|
||||||
|
const newCanaryObject =
|
||||||
|
canaryDeploymentHelper.getNewCanaryResource(
|
||||||
|
inputObject,
|
||||||
|
canaryReplicaCount
|
||||||
|
)
|
||||||
const newBaselineObject =
|
const newBaselineObject =
|
||||||
canaryDeploymentHelper.getBaselineDeploymentFromStableDeployment(
|
canaryDeploymentHelper.getNewBaselineResource(
|
||||||
stableObject,
|
stableObject,
|
||||||
canaryReplicaCount
|
canaryReplicaCount
|
||||||
)
|
)
|
||||||
|
newObjectsList.push(newCanaryObject)
|
||||||
newObjectsList.push(newBaselineObject)
|
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 {
|
} else {
|
||||||
// Update non deployment entity or stable deployment as it is
|
// Update non deployment entity as it is
|
||||||
newObjectsList.push(inputObject)
|
newObjectsList.push(inputObject)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
})
|
||||||
core.debug(
|
|
||||||
`deploying canary objects with SMI: \n ${JSON.stringify(newObjectsList)}`
|
|
||||||
)
|
|
||||||
const newFilePaths = fileHelper.writeObjectsToFile(newObjectsList)
|
const newFilePaths = fileHelper.writeObjectsToFile(newObjectsList)
|
||||||
const forceDeployment = core.getInput('force').toLowerCase() === 'true'
|
const forceDeployment = core.getInput('force').toLowerCase() === 'true'
|
||||||
const result = await kubectl.apply(newFilePaths, forceDeployment)
|
const result = await kubectl.apply(newFilePaths, forceDeployment)
|
||||||
const svcDeploymentFiles = await createCanaryService(kubectl, filePaths)
|
await createCanaryService(kubectl, filePaths)
|
||||||
newFilePaths.push(...svcDeploymentFiles)
|
return {result, newFilePaths}
|
||||||
return {execResult: result, manifestFiles: newFilePaths}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createCanaryService(
|
async function createCanaryService(kubectl: Kubectl, filePaths: string[]) {
|
||||||
kubectl: Kubectl,
|
|
||||||
filePaths: string[]
|
|
||||||
): Promise<string[]> {
|
|
||||||
const newObjectsList = []
|
const newObjectsList = []
|
||||||
const trafficObjectsList: string[] = []
|
const trafficObjectsList = []
|
||||||
|
|
||||||
for (const filePath of filePaths) {
|
for (const filePath of filePaths) {
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
const fileContents = fs.readFileSync(filePath).toString()
|
||||||
@@ -118,7 +93,6 @@ async function createCanaryService(
|
|||||||
const kind = inputObject.kind
|
const kind = inputObject.kind
|
||||||
|
|
||||||
if (isServiceEntity(kind)) {
|
if (isServiceEntity(kind)) {
|
||||||
core.debug(`Creating services for ${kind} ${name}`)
|
|
||||||
const newCanaryServiceObject =
|
const newCanaryServiceObject =
|
||||||
canaryDeploymentHelper.getNewCanaryResource(inputObject)
|
canaryDeploymentHelper.getNewCanaryResource(inputObject)
|
||||||
newObjectsList.push(newCanaryServiceObject)
|
newObjectsList.push(newCanaryServiceObject)
|
||||||
@@ -181,7 +155,7 @@ async function createCanaryService(
|
|||||||
name
|
name
|
||||||
)
|
)
|
||||||
trafficObjectsList.push(
|
trafficObjectsList.push(
|
||||||
await updateTrafficSplitObject(kubectl, name)
|
updateTrafficSplitObject(kubectl, name)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,7 +169,6 @@ async function createCanaryService(
|
|||||||
|
|
||||||
const result = await kubectl.apply(manifestFiles, forceDeployment)
|
const result = await kubectl.apply(manifestFiles, forceDeployment)
|
||||||
checkForErrors([result])
|
checkForErrors([result])
|
||||||
return manifestFiles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function redirectTrafficToCanaryDeployment(
|
export async function redirectTrafficToCanaryDeployment(
|
||||||
@@ -208,8 +181,8 @@ export async function redirectTrafficToCanaryDeployment(
|
|||||||
export async function redirectTrafficToStableDeployment(
|
export async function redirectTrafficToStableDeployment(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
manifestFilePaths: string[]
|
manifestFilePaths: string[]
|
||||||
): Promise<string[]> {
|
) {
|
||||||
return await adjustTraffic(kubectl, manifestFilePaths, 1000, 0)
|
await adjustTraffic(kubectl, manifestFilePaths, 1000, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function adjustTraffic(
|
async function adjustTraffic(
|
||||||
@@ -251,14 +224,13 @@ async function adjustTraffic(
|
|||||||
const forceDeployment = core.getInput('force').toLowerCase() === 'true'
|
const forceDeployment = core.getInput('force').toLowerCase() === 'true'
|
||||||
const result = await kubectl.apply(trafficSplitManifests, forceDeployment)
|
const result = await kubectl.apply(trafficSplitManifests, forceDeployment)
|
||||||
checkForErrors([result])
|
checkForErrors([result])
|
||||||
return trafficSplitManifests
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateTrafficSplitObject(
|
async function updateTrafficSplitObject(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
serviceName: string
|
serviceName: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const percentage = parseInt(core.getInput('percentage', {required: true}))
|
const percentage = parseInt(core.getInput('percentage'))
|
||||||
if (percentage < 0 || percentage > 100)
|
if (percentage < 0 || percentage > 100)
|
||||||
throw Error('Percentage must be between 0 and 100')
|
throw Error('Percentage must be between 0 and 100')
|
||||||
|
|
||||||
@@ -269,9 +241,9 @@ async function updateTrafficSplitObject(
|
|||||||
core.debug(
|
core.debug(
|
||||||
'Creating the traffic object with canary weight: ' +
|
'Creating the traffic object with canary weight: ' +
|
||||||
baselineAndCanaryWeight +
|
baselineAndCanaryWeight +
|
||||||
', baseline weight: ' +
|
',baseling weight: ' +
|
||||||
baselineAndCanaryWeight +
|
baselineAndCanaryWeight +
|
||||||
', stable weight: ' +
|
',stable: ' +
|
||||||
stableDeploymentWeight
|
stableDeploymentWeight
|
||||||
)
|
)
|
||||||
return await createTrafficSplitManifestFile(
|
return await createTrafficSplitManifestFile(
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ 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'
|
|
||||||
|
|
||||||
export async function deployManifests(
|
export async function deployManifests(
|
||||||
files: string[],
|
files: string[],
|
||||||
@@ -50,13 +48,13 @@ export async function deployManifests(
|
|||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
switch (deploymentStrategy) {
|
switch (deploymentStrategy) {
|
||||||
case DeploymentStrategy.CANARY: {
|
case DeploymentStrategy.CANARY: {
|
||||||
const canaryDeployResult: DeployResult =
|
const {result, newFilePaths} =
|
||||||
trafficSplitMethod == TrafficSplitMethod.SMI
|
trafficSplitMethod == TrafficSplitMethod.SMI
|
||||||
? await deploySMICanary(files, kubectl)
|
? await deploySMICanary(files, kubectl)
|
||||||
: await deployPodCanary(files, kubectl)
|
: await deployPodCanary(files, kubectl)
|
||||||
|
|
||||||
checkForErrors([canaryDeployResult.execResult])
|
checkForErrors([result])
|
||||||
return canaryDeployResult.manifestFiles
|
return newFilePaths
|
||||||
}
|
}
|
||||||
|
|
||||||
case DeploymentStrategy.BLUE_GREEN: {
|
case DeploymentStrategy.BLUE_GREEN: {
|
||||||
@@ -75,12 +73,7 @@ export async function deployManifests(
|
|||||||
)
|
)
|
||||||
|
|
||||||
checkForErrors([blueGreenDeployment.deployResult.execResult])
|
checkForErrors([blueGreenDeployment.deployResult.execResult])
|
||||||
const deployedManifestFiles =
|
return blueGreenDeployment.deployResult.manifestFiles
|
||||||
blueGreenDeployment.deployResult.manifestFiles
|
|
||||||
core.debug(
|
|
||||||
`from blue-green service, deployed manifest files are ${deployedManifestFiles}`
|
|
||||||
)
|
|
||||||
return deployedManifestFiles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case DeploymentStrategy.BASIC: {
|
case DeploymentStrategy.BASIC: {
|
||||||
@@ -150,15 +143,8 @@ export async function annotateAndLabelResources(
|
|||||||
resourceTypes: Resource[],
|
resourceTypes: Resource[],
|
||||||
allPods: any
|
allPods: any
|
||||||
) {
|
) {
|
||||||
const defaultWorkflowFileName = 'k8s-deploy-failed-workflow-annotation'
|
|
||||||
const githubToken = core.getInput('token')
|
const githubToken = core.getInput('token')
|
||||||
let workflowFilePath
|
const workflowFilePath = await getWorkflowFilePath(githubToken)
|
||||||
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()
|
||||||
@@ -171,11 +157,8 @@ export async function annotateAndLabelResources(
|
|||||||
annotationKeyLabel,
|
annotationKeyLabel,
|
||||||
workflowFilePath,
|
workflowFilePath,
|
||||||
deploymentConfig
|
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(
|
||||||
@@ -195,18 +178,6 @@ async function annotateResources(
|
|||||||
annotationKey
|
annotationKey
|
||||||
)
|
)
|
||||||
|
|
||||||
if (core.isDebug()) {
|
|
||||||
core.debug(`files getting annotated are ${JSON.stringify(files)}`)
|
|
||||||
for (const filePath of files) {
|
|
||||||
core.debug('printing objects getting annotated...')
|
|
||||||
const fileContents = fs.readFileSync(filePath).toString()
|
|
||||||
const inputObjects = yaml.safeLoadAll(fileContents)
|
|
||||||
for (const inputObject of inputObjects) {
|
|
||||||
core.debug(`object: ${JSON.stringify(inputObject)}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const annotationKeyValStr = `${annotationKey}=${getWorkflowAnnotations(
|
const annotationKeyValStr = `${annotationKey}=${getWorkflowAnnotations(
|
||||||
lastSuccessSha,
|
lastSuccessSha,
|
||||||
workflowFilePath,
|
workflowFilePath,
|
||||||
@@ -218,25 +189,10 @@ async function annotateResources(
|
|||||||
)
|
)
|
||||||
if (annotateNamespace) {
|
if (annotateNamespace) {
|
||||||
annotateResults.push(
|
annotateResults.push(
|
||||||
await kubectl.annotate(
|
await kubectl.annotate('namespace', namespace, annotationKeyValStr)
|
||||||
'namespace',
|
|
||||||
namespace,
|
|
||||||
annotationKeyValStr,
|
|
||||||
namespace
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for (const file of files) {
|
annotateResults.push(await kubectl.annotateFiles(files, annotationKeyValStr))
|
||||||
try {
|
|
||||||
const annotateResult = await kubectl.annotateFiles(
|
|
||||||
file,
|
|
||||||
annotationKeyValStr
|
|
||||||
)
|
|
||||||
annotateResults.push(annotateResult)
|
|
||||||
} catch (e) {
|
|
||||||
core.warning(`failed to annotate resource: ${e}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const resource of resourceTypes) {
|
for (const resource of resourceTypes) {
|
||||||
if (
|
if (
|
||||||
@@ -248,7 +204,6 @@ async function annotateResources(
|
|||||||
kubectl,
|
kubectl,
|
||||||
resource.type,
|
resource.type,
|
||||||
resource.name,
|
resource.name,
|
||||||
resource.namespace,
|
|
||||||
annotationKeyValStr,
|
annotationKeyValStr,
|
||||||
allPods
|
allPods
|
||||||
)
|
)
|
||||||
@@ -271,14 +226,5 @@ async function labelResources(
|
|||||||
`workflow=${cleanLabel(label)}`
|
`workflow=${cleanLabel(label)}`
|
||||||
]
|
]
|
||||||
|
|
||||||
const labelResults = []
|
checkForErrors([await kubectl.labelFiles(files, labels)], true)
|
||||||
for (const file of files) {
|
|
||||||
try {
|
|
||||||
const labelResult = await kubectl.labelFiles(file, labels)
|
|
||||||
labelResults.push(labelResult)
|
|
||||||
} catch (e) {
|
|
||||||
core.warning(`failed to annotate resource: ${e}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkForErrors(labelResults, true)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
export interface Succeeded<T> {
|
|
||||||
readonly succeeded: true
|
|
||||||
readonly result: T
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Failed {
|
|
||||||
readonly succeeded: false
|
|
||||||
readonly error: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Errorable<T> = Succeeded<T> | Failed
|
|
||||||
|
|
||||||
export function succeeded<T>(e: Errorable<T>): e is Succeeded<T> {
|
|
||||||
return e.succeeded
|
|
||||||
}
|
|
||||||
|
|
||||||
export function failed<T>(e: Errorable<T>): e is Failed {
|
|
||||||
return !e.succeeded
|
|
||||||
}
|
|
||||||
|
|
||||||
export function map<T, U>(e: Errorable<T>, fn: (t: T) => U): Errorable<U> {
|
|
||||||
if (failed(e)) {
|
|
||||||
return {succeeded: false, error: e.error}
|
|
||||||
}
|
|
||||||
return {succeeded: true, result: fn(e.result)}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function combine<T>(es: Errorable<T>[]): Errorable<T[]> {
|
|
||||||
const failures = es.filter(failed)
|
|
||||||
if (failures.length > 0) {
|
|
||||||
return {
|
|
||||||
succeeded: false,
|
|
||||||
error: failures.map((f) => f.error).join('\n')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
succeeded: true,
|
|
||||||
result: es.map((e) => (e as Succeeded<T>).result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getErrorMessage(error: unknown) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
return error.message
|
|
||||||
}
|
|
||||||
return String(error)
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ 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
|
||||||
@@ -17,7 +16,6 @@ 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 {
|
||||||
|
|||||||
+12
-193
@@ -3,6 +3,7 @@ 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'
|
||||||
@@ -37,7 +38,6 @@ 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', () => {
|
describe('default namespace behavior', () => {
|
||||||
const kubectl = new Kubectl(kubectlPath, defaultNamespace)
|
const kubectl = new Kubectl(kubectlPath, defaultNamespace)
|
||||||
@@ -48,6 +48,17 @@ describe('Kubectl class', () => {
|
|||||||
return execReturn
|
return execReturn
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('omits default namespace from commands', () => {
|
||||||
|
it('executes a command without appending --namespace arg', async () => {
|
||||||
|
// no args
|
||||||
|
const command = 'command'
|
||||||
|
expect(await kubectl.executeCommand(command)).toBe(execReturn)
|
||||||
|
expect(exec.getExecOutput).toBeCalledWith(kubectlPath, [command], {
|
||||||
|
silent: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with a success exec return in testNamespace', () => {
|
describe('with a success exec return in testNamespace', () => {
|
||||||
@@ -122,26 +133,6 @@ 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 () => {
|
||||||
@@ -160,26 +151,6 @@ 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 () => {
|
||||||
@@ -205,27 +176,6 @@ 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 () => {
|
||||||
@@ -246,22 +196,6 @@ 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 () => {
|
||||||
@@ -282,22 +216,6 @@ 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 () => {
|
||||||
@@ -318,21 +236,6 @@ 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 () => {
|
||||||
@@ -353,21 +256,6 @@ 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 () => {
|
||||||
@@ -396,20 +284,6 @@ 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 () => {
|
||||||
@@ -428,22 +302,6 @@ 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 () => {
|
||||||
@@ -474,14 +332,6 @@ 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 () => {
|
||||||
@@ -492,14 +342,6 @@ 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}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -522,27 +364,4 @@ describe('Kubectl class', () => {
|
|||||||
const result = await kubectl.getNewReplicaSet(deployment)
|
const result = await kubectl.getNewReplicaSet(deployment)
|
||||||
expect(result).toBe(name)
|
expect(result).toBe(name)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('executes with constructor flags', async () => {
|
|
||||||
const skipTls = true
|
|
||||||
const kubectl = new Kubectl(kubectlPath, testNamespace, skipTls)
|
|
||||||
|
|
||||||
jest.spyOn(exec, 'getExecOutput').mockImplementation(async () => {
|
|
||||||
return {exitCode: 0, stderr: '', stdout: ''}
|
|
||||||
})
|
|
||||||
|
|
||||||
const command = 'command'
|
|
||||||
kubectl.executeCommand(command)
|
|
||||||
expect(exec.getExecOutput).toBeCalledWith(
|
|
||||||
kubectlPath,
|
|
||||||
[command, '--insecure-skip-tls-verify', '--namespace', testNamespace],
|
|
||||||
{silent: false}
|
|
||||||
)
|
|
||||||
|
|
||||||
const kubectlNoFlags = new Kubectl(kubectlPath)
|
|
||||||
kubectlNoFlags.executeCommand(command)
|
|
||||||
expect(exec.getExecOutput).toBeCalledWith(kubectlPath, [command], {
|
|
||||||
silent: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
+37
-81
@@ -7,7 +7,6 @@ import * as io from '@actions/io'
|
|||||||
export interface Resource {
|
export interface Resource {
|
||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
namespace?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Kubectl {
|
export class Kubectl {
|
||||||
@@ -20,7 +19,7 @@ export class Kubectl {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
kubectlPath: string,
|
kubectlPath: string,
|
||||||
namespace: string = '',
|
namespace: string = 'default',
|
||||||
ignoreSSLErrors: boolean = false,
|
ignoreSSLErrors: boolean = false,
|
||||||
resourceGroup: string = '',
|
resourceGroup: string = '',
|
||||||
name: string = ''
|
name: string = ''
|
||||||
@@ -47,7 +46,7 @@ export class Kubectl {
|
|||||||
]
|
]
|
||||||
if (force) applyArgs.push('--force')
|
if (force) applyArgs.push('--force')
|
||||||
|
|
||||||
return await this.execute(applyArgs.concat(this.getFlags()))
|
return await this.execute(applyArgs)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
core.debug('Kubectl apply failed:' + err)
|
core.debug('Kubectl apply failed:' + err)
|
||||||
}
|
}
|
||||||
@@ -56,43 +55,27 @@ 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].concat(
|
['describe', resourceType, resourceName],
|
||||||
this.getFlags(namespace)
|
|
||||||
),
|
|
||||||
silent
|
silent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getNewReplicaSet(deployment: string, namespace?: string) {
|
public async getNewReplicaSet(deployment: string) {
|
||||||
const result = await this.describe(
|
const result = await this.describe('deployment', deployment, true)
|
||||||
'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]
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,8 +85,7 @@ 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',
|
||||||
@@ -111,31 +93,27 @@ 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)
|
|
||||||
core.debug(`annotating ${filesToAnnotate} with annotation ${annotation}`)
|
|
||||||
const args = [
|
const args = [
|
||||||
'annotate',
|
'annotate',
|
||||||
'-f',
|
'-f',
|
||||||
filesToAnnotate,
|
createInlineArray(files),
|
||||||
annotation,
|
annotation,
|
||||||
'--overwrite'
|
'--overwrite'
|
||||||
].concat(this.getFlags(namespace))
|
]
|
||||||
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',
|
||||||
@@ -143,78 +121,56 @@ 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(
|
return await this.execute(['get', 'pods', '-o', 'json'], true)
|
||||||
['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', 'status', `${resourceType}/${name}`].concat(
|
'rollout',
|
||||||
this.getFlags(namespace)
|
'status',
|
||||||
)
|
`${resourceType}/${name}`
|
||||||
)
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getResource(
|
public async getResource(
|
||||||
resourceType: string,
|
resourceType: string,
|
||||||
name: string,
|
name: string
|
||||||
silentFailure: boolean = false,
|
|
||||||
namespace?: string
|
|
||||||
): Promise<ExecOutput> {
|
): Promise<ExecOutput> {
|
||||||
core.debug(
|
return await this.execute([
|
||||||
'fetching resource of type ' + resourceType + ' and name ' + name
|
'get',
|
||||||
)
|
`${resourceType}/${name}`,
|
||||||
return await this.execute(
|
'-o',
|
||||||
['get', `${resourceType}/${name}`, '-o', 'json'].concat(
|
'json'
|
||||||
this.getFlags(namespace)
|
])
|
||||||
),
|
|
||||||
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')
|
||||||
const a = args ? [args] : []
|
return args ? this.execute([command, args]) : this.execute([command])
|
||||||
return this.execute([command, ...a.concat(this.getFlags())])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public delete(args: string | string[], namespace?: string) {
|
public delete(args: string | string[]) {
|
||||||
if (typeof args === 'string')
|
if (typeof args === 'string') return this.execute(['delete', args])
|
||||||
return this.execute(['delete', args].concat(this.getFlags(namespace)))
|
return this.execute(['delete', ...args])
|
||||||
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) {
|
||||||
core.debug(`Kubectl run with command: ${this.kubectlPath} ${args}`)
|
|
||||||
|
|
||||||
return await getExecOutput(this.kubectlPath, args, {
|
|
||||||
silent
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getFlags(namespaceOverride?: string): string[] {
|
|
||||||
const flags = []
|
|
||||||
if (this.ignoreSSLErrors) {
|
if (this.ignoreSSLErrors) {
|
||||||
flags.push('--insecure-skip-tls-verify')
|
args.push('--insecure-skip-tls-verify')
|
||||||
}
|
}
|
||||||
|
if (this.namespace && this.namespace != 'default') {
|
||||||
const ns = namespaceOverride || this.namespace
|
args = args.concat(['--namespace', this.namespace])
|
||||||
if (ns) {
|
|
||||||
flags.push('--namespace', ns)
|
|
||||||
}
|
}
|
||||||
|
core.debug(`Kubectl run with command: ${this.kubectlPath} ${args}`)
|
||||||
return flags
|
return await getExecOutput(this.kubectlPath, args, {silent})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import {PrivateKubectl} from './privatekubectl'
|
|
||||||
import * as exec from '@actions/exec'
|
|
||||||
|
|
||||||
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 mockKube = new PrivateKubectl(
|
|
||||||
'kubectlPath',
|
|
||||||
'namespace',
|
|
||||||
true,
|
|
||||||
'resourceGroup',
|
|
||||||
'resourceName'
|
|
||||||
)
|
|
||||||
|
|
||||||
it('should extract filenames correctly', () => {
|
|
||||||
expect(mockKube.extractFilesnames(testString)).toEqual(
|
|
||||||
'test.yml test2.yml test3.yml test4.yml test5.yml'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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}`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
+15
-63
@@ -1,5 +1,4 @@
|
|||||||
import {Kubectl} from './kubectl'
|
import {Kubectl} from './kubectl'
|
||||||
import * as 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 * as os from 'os'
|
||||||
@@ -11,11 +10,7 @@ export class PrivateKubectl extends Kubectl {
|
|||||||
args.unshift('kubectl')
|
args.unshift('kubectl')
|
||||||
let kubectlCmd = args.join(' ')
|
let kubectlCmd = args.join(' ')
|
||||||
let addFileFlag = false
|
let addFileFlag = false
|
||||||
let eo = <ExecOptions>{
|
let eo = <ExecOptions>{silent}
|
||||||
silent: true,
|
|
||||||
failOnStdErr: false,
|
|
||||||
ignoreReturnCode: true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.containsFilenames(kubectlCmd)) {
|
if (this.containsFilenames(kubectlCmd)) {
|
||||||
// For private clusters, files will referenced solely by their basename
|
// For private clusters, files will referenced solely by their basename
|
||||||
@@ -23,13 +18,6 @@ export class PrivateKubectl extends Kubectl {
|
|||||||
addFileFlag = true
|
addFileFlag = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.resourceGroup === '') {
|
|
||||||
throw Error('Resource group must be specified for private cluster')
|
|
||||||
}
|
|
||||||
if (this.name === '') {
|
|
||||||
throw Error('Cluster name must be specified for private cluster')
|
|
||||||
}
|
|
||||||
|
|
||||||
const privateClusterArgs = [
|
const privateClusterArgs = [
|
||||||
'aks',
|
'aks',
|
||||||
'command',
|
'command',
|
||||||
@@ -39,7 +27,7 @@ export class PrivateKubectl extends Kubectl {
|
|||||||
'--name',
|
'--name',
|
||||||
this.name,
|
this.name,
|
||||||
'--command',
|
'--command',
|
||||||
`${kubectlCmd}`
|
kubectlCmd
|
||||||
]
|
]
|
||||||
|
|
||||||
if (addFileFlag) {
|
if (addFileFlag) {
|
||||||
@@ -64,35 +52,7 @@ export class PrivateKubectl extends Kubectl {
|
|||||||
core.debug(
|
core.debug(
|
||||||
`private cluster Kubectl run with invoke command: ${kubectlCmd}`
|
`private cluster Kubectl run with invoke command: ${kubectlCmd}`
|
||||||
)
|
)
|
||||||
|
return await getExecOutput('az', privateClusterArgs, eo)
|
||||||
const allArgs = [...privateClusterArgs, '-o', 'json']
|
|
||||||
core.debug(`full form of az command: az ${allArgs.join(' ')}`)
|
|
||||||
const runOutput = await getExecOutput('az', allArgs, eo)
|
|
||||||
core.debug(
|
|
||||||
`from kubectl private cluster command got run output ${JSON.stringify(
|
|
||||||
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(
|
|
||||||
runOutput.stdout
|
|
||||||
)
|
|
||||||
if (!silent) core.info(runObj.logs)
|
|
||||||
if (runObj.exitCode !== 0) {
|
|
||||||
throw Error(`failed private cluster Kubectl command: ${kubectlCmd}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
exitCode: runObj.exitCode,
|
|
||||||
stdout: runObj.logs,
|
|
||||||
stderr: ''
|
|
||||||
} as ExecOutput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private replaceFilnamesWithBasenames(kubectlCmd: string) {
|
private replaceFilnamesWithBasenames(kubectlCmd: string) {
|
||||||
@@ -111,31 +71,23 @@ export class PrivateKubectl extends Kubectl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public extractFilesnames(strToParse: string) {
|
public extractFilesnames(strToParse: string) {
|
||||||
const fileNames: string[] = []
|
let start = strToParse.indexOf('-filename')
|
||||||
const argv = minimist(strToParse.split(' '))
|
let offset = 7
|
||||||
const fArg = 'f'
|
|
||||||
const filenameArg = 'filename'
|
|
||||||
|
|
||||||
fileNames.push(...this.extractFilesFromMinimist(argv, fArg))
|
if (start == -1) {
|
||||||
fileNames.push(...this.extractFilesFromMinimist(argv, filenameArg))
|
start = strToParse.indexOf('-f')
|
||||||
|
|
||||||
return fileNames.join(' ')
|
if (start == -1) {
|
||||||
}
|
return ''
|
||||||
|
|
||||||
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(','))
|
|
||||||
}
|
}
|
||||||
|
offset = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return toReturn
|
let temp = strToParse.substring(start + offset)
|
||||||
|
let end = temp.indexOf(' -')
|
||||||
|
|
||||||
|
//End could be case where the -f flag was last, or -f is followed by some additonal flag and it's arguments
|
||||||
|
return temp.substring(3, end == -1 ? temp.length : end).trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
private containsFilenames(str: string) {
|
private containsFilenames(str: string) {
|
||||||
|
|||||||
@@ -1,45 +1,11 @@
|
|||||||
import {
|
import {getFilesFromDirectories} from './fileUtils'
|
||||||
getFilesFromDirectoriesAndURLs,
|
|
||||||
getTempDirectory,
|
|
||||||
urlFileKind,
|
|
||||||
writeYamlFromURLToFile
|
|
||||||
} from './fileUtils'
|
|
||||||
|
|
||||||
import * as yaml from 'js-yaml'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import {succeeded} from '../types/errorable'
|
|
||||||
|
|
||||||
const sampleYamlUrl =
|
|
||||||
'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 () => {
|
it('detects files in nested directories and ignores non-manifest files and empty dirs', () => {
|
||||||
const tempFile = await writeYamlFromURLToFile(sampleYamlUrl, 0)
|
|
||||||
const fileContents = fs.readFileSync(tempFile).toString()
|
|
||||||
const inputObjects = yaml.safeLoadAll(fileContents)
|
|
||||||
expect(inputObjects).toHaveLength(1)
|
|
||||||
|
|
||||||
for (const obj of inputObjects) {
|
|
||||||
expect(obj.metadata.name).toBe('nginx-deployment')
|
|
||||||
expect(obj.kind).toBe('Deployment')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('fails when a bad URL is given among other files', async () => {
|
|
||||||
const badUrl = 'https://www.github.com'
|
|
||||||
|
|
||||||
const testPath = path.join('test', 'unit', 'manifests')
|
const testPath = path.join('test', 'unit', 'manifests')
|
||||||
await expect(
|
const testSearch: string[] = getFilesFromDirectories([testPath])
|
||||||
getFilesFromDirectoriesAndURLs([testPath, badUrl])
|
|
||||||
).rejects.toThrow()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('detects files in nested directories and ignores non-manifest files and empty dirs', async () => {
|
|
||||||
const testPath = path.join('test', 'unit', 'manifests')
|
|
||||||
const testSearch: string[] = await getFilesFromDirectoriesAndURLs([
|
|
||||||
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/deep-ingress.yaml',
|
||||||
@@ -51,18 +17,13 @@ describe('File utils', () => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
// is there a more efficient way to test equality w random order?
|
// is there a more efficient way to test equality w random order?
|
||||||
expect(testSearch).toHaveLength(8)
|
expect(testSearch).toHaveLength(7)
|
||||||
expectedManifests.forEach((fileName) => {
|
expectedManifests.forEach((fileName) => {
|
||||||
if (fileName.startsWith('test/unit')) {
|
expect(testSearch).toContain(fileName)
|
||||||
expect(testSearch).toContain(fileName)
|
|
||||||
} else {
|
|
||||||
expect(fileName.includes(urlFileKind)).toBe(true)
|
|
||||||
expect(fileName.startsWith(getTempDirectory()))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('crashes when an invalid file is provided', async () => {
|
it('crashes when an invalid file is provided', () => {
|
||||||
const badPath = path.join('test', 'unit', 'manifests', 'nonexistent.yaml')
|
const badPath = path.join('test', 'unit', 'manifests', 'nonexistent.yaml')
|
||||||
const goodPath = path.join(
|
const goodPath = path.join(
|
||||||
'test',
|
'test',
|
||||||
@@ -71,12 +32,12 @@ describe('File utils', () => {
|
|||||||
'manifest_test_dir'
|
'manifest_test_dir'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
expect(() => {
|
||||||
getFilesFromDirectoriesAndURLs([badPath, goodPath])
|
getFilesFromDirectories([badPath, goodPath])
|
||||||
).rejects.toThrowError()
|
}).toThrowError()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("doesn't duplicate files when nested dir included", async () => {
|
it("doesn't duplicate files when nested dir included", () => {
|
||||||
const outerPath = path.join('test', 'unit', 'manifests')
|
const outerPath = path.join('test', 'unit', 'manifests')
|
||||||
const fileAtOuter = path.join(
|
const fileAtOuter = path.join(
|
||||||
'test',
|
'test',
|
||||||
@@ -92,16 +53,11 @@ describe('File utils', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await getFilesFromDirectoriesAndURLs([
|
getFilesFromDirectories([outerPath, fileAtOuter, innerPath])
|
||||||
outerPath,
|
|
||||||
fileAtOuter,
|
|
||||||
innerPath
|
|
||||||
])
|
|
||||||
).toHaveLength(7)
|
).toHaveLength(7)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws an error for an invalid URL', async () => {
|
|
||||||
const badUrl = 'https://www.github.com'
|
|
||||||
await expect(writeYamlFromURLToFile(badUrl, 0)).rejects.toBeTruthy()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// files that don't exist / nested files that don't exist / something else with non-manifest
|
||||||
|
// lots of combinations of pointing to a directory and non yaml/yaml file
|
||||||
|
// similarly named files in different folders
|
||||||
|
|||||||
+4
-103
@@ -1,15 +1,8 @@
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
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'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import * as yaml from 'js-yaml'
|
|
||||||
import {Errorable, succeeded, failed, Failed} from '../types/errorable'
|
|
||||||
import {getCurrentTime} from './timeUtils'
|
import {getCurrentTime} from './timeUtils'
|
||||||
import {isHttpUrl} from './githubUtils'
|
|
||||||
import {K8sObject} from '../types/k8sObject'
|
|
||||||
|
|
||||||
export const urlFileKind = 'urlfile'
|
|
||||||
|
|
||||||
export function getTempDirectory(): string {
|
export function getTempDirectory(): string {
|
||||||
return process.env['runner.tempDirectory'] || os.tmpdir()
|
return process.env['runner.tempDirectory'] || os.tmpdir()
|
||||||
@@ -69,27 +62,12 @@ function getManifestFileName(kind: string, name: string) {
|
|||||||
return path.join(tempDirectory, path.basename(filePath))
|
return path.join(tempDirectory, path.basename(filePath))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFilesFromDirectoriesAndURLs(
|
export function getFilesFromDirectories(filePaths: string[]): string[] {
|
||||||
filePaths: string[]
|
|
||||||
): Promise<string[]> {
|
|
||||||
const fullPathSet: Set<string> = new Set<string>()
|
const fullPathSet: Set<string> = new Set<string>()
|
||||||
|
|
||||||
let fileCounter = 0
|
filePaths.forEach((fileName) => {
|
||||||
for (const fileName of filePaths) {
|
|
||||||
try {
|
try {
|
||||||
if (isHttpUrl(fileName)) {
|
if (fs.lstatSync(fileName).isDirectory()) {
|
||||||
try {
|
|
||||||
const tempFilePath: string = await writeYamlFromURLToFile(
|
|
||||||
fileName,
|
|
||||||
fileCounter++
|
|
||||||
)
|
|
||||||
fullPathSet.add(tempFilePath)
|
|
||||||
} catch (e) {
|
|
||||||
throw Error(
|
|
||||||
`encountered error trying to pull YAML from URL ${fileName}: ${e}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (fs.lstatSync(fileName).isDirectory()) {
|
|
||||||
recurisveManifestGetter(fileName).forEach((file) => {
|
recurisveManifestGetter(fileName).forEach((file) => {
|
||||||
fullPathSet.add(file)
|
fullPathSet.add(file)
|
||||||
})
|
})
|
||||||
@@ -108,86 +86,9 @@ export async function getFilesFromDirectoriesAndURLs(
|
|||||||
`Exception occurred while reading the file ${fileName}: ${ex}`
|
`Exception occurred while reading the file ${fileName}: ${ex}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const arr = Array.from(fullPathSet)
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function writeYamlFromURLToFile(
|
|
||||||
url: string,
|
|
||||||
fileNumber: number
|
|
||||||
): Promise<string> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
https
|
|
||||||
.get(url, async (response) => {
|
|
||||||
const code = response.statusCode ?? 0
|
|
||||||
if (code >= 400) {
|
|
||||||
reject(
|
|
||||||
Error(
|
|
||||||
`received response status ${response.statusMessage} from url ${url}`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetPath = getManifestFileName(
|
|
||||||
urlFileKind,
|
|
||||||
fileNumber.toString()
|
|
||||||
)
|
|
||||||
// save the file to disk
|
|
||||||
const fileWriter = fs
|
|
||||||
.createWriteStream(targetPath)
|
|
||||||
.on('finish', () => {
|
|
||||||
const verification = verifyYaml(targetPath, url)
|
|
||||||
if (succeeded(verification)) {
|
|
||||||
core.debug(
|
|
||||||
`outputting YAML contents from ${url} to ${targetPath}: ${JSON.stringify(
|
|
||||||
verification.result
|
|
||||||
)}`
|
|
||||||
)
|
|
||||||
resolve(targetPath)
|
|
||||||
} else {
|
|
||||||
reject(verification.error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
response.pipe(fileWriter)
|
|
||||||
})
|
|
||||||
.on('error', (error) => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
function verifyYaml(filepath: string, url: string): Errorable<K8sObject[]> {
|
return Array.from(fullPathSet)
|
||||||
const fileContents = fs.readFileSync(filepath).toString()
|
|
||||||
let inputObjects
|
|
||||||
try {
|
|
||||||
inputObjects = yaml.safeLoadAll(fileContents)
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
succeeded: false,
|
|
||||||
error: `failed to parse manifest from url ${url}: ${e}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inputObjects || inputObjects.length == 0) {
|
|
||||||
return {
|
|
||||||
succeeded: false,
|
|
||||||
error: `failed to parse manifest from url ${url}: no objects detected in manifest`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const obj of inputObjects) {
|
|
||||||
if (!obj.kind || !obj.apiVersion || !obj.metadata) {
|
|
||||||
return {
|
|
||||||
succeeded: false,
|
|
||||||
error: `failed to parse manifest from ${url}: missing fields`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {succeeded: true, result: inputObjects}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function recurisveManifestGetter(dirName: string): string[] {
|
function recurisveManifestGetter(dirName: string): string[] {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ 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
|
||||||
@@ -32,12 +30,7 @@ export async function getLastSuccessfulRunSha(
|
|||||||
annotationKey: string
|
annotationKey: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const result = await kubectl.getResource(
|
const result = await kubectl.getResource('namespace', namespaceName)
|
||||||
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
|
||||||
@@ -60,13 +53,12 @@ export async function annotateChildPods(
|
|||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
resourceType: string,
|
resourceType: string,
|
||||||
resourceName: string,
|
resourceName: string,
|
||||||
namespace: string | undefined,
|
|
||||||
annotationKeyValStr: string,
|
annotationKeyValStr: string,
|
||||||
allPods
|
allPods
|
||||||
): 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, namespace)
|
owner = await kubectl.getNewReplicaSet(resourceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandExecutionResults = []
|
const commandExecutionResults = []
|
||||||
@@ -80,8 +72,7 @@ export async function annotateChildPods(
|
|||||||
kubectl.annotate(
|
kubectl.annotate(
|
||||||
'pod',
|
'pod',
|
||||||
pod.metadata.name,
|
pod.metadata.name,
|
||||||
annotationKeyValStr,
|
annotationKeyValStr
|
||||||
namespace
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import {Kubectl, Resource} from '../types/kubectl'
|
|||||||
import {checkForErrors} from './kubectlUtils'
|
import {checkForErrors} from './kubectlUtils'
|
||||||
import {sleep} from './timeUtils'
|
import {sleep} from './timeUtils'
|
||||||
|
|
||||||
const IS_SILENT = false
|
|
||||||
const POD = 'pod'
|
|
||||||
|
|
||||||
export async function checkManifestStability(
|
export async function checkManifestStability(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
resources: Resource[]
|
resources: Resource[]
|
||||||
@@ -23,35 +20,24 @@ 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(
|
await kubectl.describe(resource.type, resource.name)
|
||||||
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)
|
await checkPodStatus(kubectl, resource.name)
|
||||||
} 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(
|
await kubectl.describe(resource.type, resource.name)
|
||||||
resource.type,
|
|
||||||
resource.name,
|
|
||||||
IS_SILENT,
|
|
||||||
resource.namespace
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -59,11 +45,14 @@ export async function checkManifestStability(
|
|||||||
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE
|
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const service = await getService(kubectl, resource)
|
const service = await getService(kubectl, resource.name)
|
||||||
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(kubectl, resource)
|
await waitForServiceExternalIPAssignment(
|
||||||
|
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}`
|
||||||
@@ -74,12 +63,7 @@ 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(
|
await kubectl.describe(resource.type, resource.name)
|
||||||
resource.type,
|
|
||||||
resource.name,
|
|
||||||
IS_SILENT,
|
|
||||||
resource.namespace
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,7 +75,7 @@ export async function checkManifestStability(
|
|||||||
|
|
||||||
export async function checkPodStatus(
|
export async function checkPodStatus(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
pod: Resource
|
podName: string
|
||||||
): 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
|
||||||
@@ -101,8 +85,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: ${pod.name}`)
|
core.debug(`Polling for pod status: ${podName}`)
|
||||||
podStatus = await getPodStatus(kubectl, pod)
|
podStatus = await getPodStatus(kubectl, podName)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
podStatus &&
|
podStatus &&
|
||||||
@@ -113,42 +97,37 @@ export async function checkPodStatus(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
podStatus = await getPodStatus(kubectl, pod)
|
podStatus = await getPodStatus(kubectl, podName)
|
||||||
switch (podStatus.phase) {
|
switch (podStatus.phase) {
|
||||||
case 'Succeeded':
|
case 'Succeeded':
|
||||||
case 'Running':
|
case 'Running':
|
||||||
if (isPodReady(podStatus)) {
|
if (isPodReady(podStatus)) {
|
||||||
console.log(`pod/${pod.name} is successfully rolled out`)
|
console.log(`pod/${podName} 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/${pod.name} rollout status check timed out`)
|
core.warning(`pod/${podName} rollout status check timed out`)
|
||||||
kubectlDescribeNeeded = true
|
kubectlDescribeNeeded = true
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'Failed':
|
case 'Failed':
|
||||||
core.error(`pod/${pod.name} rollout failed`)
|
core.error(`pod/${podName} rollout failed`)
|
||||||
kubectlDescribeNeeded = true
|
kubectlDescribeNeeded = true
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
core.warning(`pod/${pod.name} rollout status: ${podStatus.phase}`)
|
core.warning(`pod/${podName} rollout status: ${podStatus.phase}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kubectlDescribeNeeded) {
|
if (kubectlDescribeNeeded) {
|
||||||
await kubectl.describe(POD, pod.name, IS_SILENT, pod.namespace)
|
await kubectl.describe('pod', podName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPodStatus(kubectl: Kubectl, pod: Resource) {
|
async function getPodStatus(kubectl: Kubectl, podName: string) {
|
||||||
const podResult = await kubectl.getResource(
|
const podResult = await kubectl.getResource('pod', podName)
|
||||||
POD,
|
|
||||||
pod.name,
|
|
||||||
IS_SILENT,
|
|
||||||
pod.namespace
|
|
||||||
)
|
|
||||||
checkForErrors([podResult])
|
checkForErrors([podResult])
|
||||||
|
|
||||||
return JSON.parse(podResult.stdout).status
|
return JSON.parse(podResult.stdout).status
|
||||||
@@ -172,12 +151,10 @@ function isPodReady(podStatus: any): boolean {
|
|||||||
return allContainersAreReady
|
return allContainersAreReady
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getService(kubectl: Kubectl, service: Resource) {
|
async function getService(kubectl: Kubectl, serviceName) {
|
||||||
const serviceResult = await kubectl.getResource(
|
const serviceResult = await kubectl.getResource(
|
||||||
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE,
|
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE,
|
||||||
service.name,
|
serviceName
|
||||||
IS_SILENT,
|
|
||||||
service.namespace
|
|
||||||
)
|
)
|
||||||
|
|
||||||
checkForErrors([serviceResult])
|
checkForErrors([serviceResult])
|
||||||
@@ -186,25 +163,25 @@ async function getService(kubectl: Kubectl, service: Resource) {
|
|||||||
|
|
||||||
async function waitForServiceExternalIPAssignment(
|
async function waitForServiceExternalIPAssignment(
|
||||||
kubectl: Kubectl,
|
kubectl: Kubectl,
|
||||||
service: Resource
|
serviceName: string
|
||||||
): 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 : ${service.name}`)
|
core.info(`Wait for service ip assignment : ${serviceName}`)
|
||||||
await sleep(sleepTimeout)
|
await sleep(sleepTimeout)
|
||||||
|
|
||||||
const status = (await getService(kubectl, service)).status
|
const status = (await getService(kubectl, serviceName)).status
|
||||||
if (isLoadBalancerIPAssigned(status)) {
|
if (isLoadBalancerIPAssigned(status)) {
|
||||||
core.info(
|
core.info(
|
||||||
`ServiceExternalIP ${service.name} ${status.loadBalancer.ingress[0].ip}`
|
`ServiceExternalIP ${serviceName} ${status.loadBalancer.ingress[0].ip}`
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
core.warning(`Wait for service ip assignment timed out ${service.name}`)
|
core.warning(`Wait for service ip assignment timed out${serviceName}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLoadBalancerIPAssigned(status: any) {
|
function isLoadBalancerIPAssigned(status: any) {
|
||||||
|
|||||||
@@ -280,8 +280,7 @@ export function getResources(
|
|||||||
) {
|
) {
|
||||||
resources.push({
|
resources.push({
|
||||||
type: inputObject.kind,
|
type: inputObject.kind,
|
||||||
name: inputObject.metadata.name,
|
name: inputObject.metadata.name
|
||||||
namespace: inputObject?.metadata?.namespace
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import {cleanLabel} from '../utilities/workflowAnnotationUtils'
|
||||||
cleanLabel,
|
|
||||||
removeInvalidLabelCharacters,
|
|
||||||
VALID_LABEL_REGEX
|
|
||||||
} from '../utilities/workflowAnnotationUtils'
|
|
||||||
|
|
||||||
describe('WorkflowAnnotationUtils', () => {
|
describe('WorkflowAnnotationUtils', () => {
|
||||||
describe('cleanLabel', () => {
|
describe('cleanLabel', () => {
|
||||||
@@ -20,14 +16,5 @@ 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,8 +2,6 @@ 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,
|
||||||
@@ -39,17 +37,11 @@ export function getWorkflowAnnotationKeyLabel(): string {
|
|||||||
* @returns cleaned label
|
* @returns cleaned label
|
||||||
*/
|
*/
|
||||||
export function cleanLabel(label: string): string {
|
export function cleanLabel(label: string): string {
|
||||||
let removedInvalidChars = removeInvalidLabelCharacters(label)
|
let removedInvalidChars = 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] || ''
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,12 @@
|
|||||||
import subprocess
|
import subprocess, sys
|
||||||
import sys
|
|
||||||
|
|
||||||
|
kind = sys.argv[1]
|
||||||
|
name = sys.argv[2]
|
||||||
|
namespace = 'test-' + sys.argv[3]
|
||||||
|
|
||||||
def delete(kind, name, namespace):
|
try:
|
||||||
try:
|
print('kubectl delete ' + kind + ' ' + name + ' -n ' + namespace)
|
||||||
if (name == "all"):
|
deletion = subprocess.Popen(['kubectl', 'delete', kind, name, '--namespace', namespace])
|
||||||
print('kubectl delete --all' + kind + ' -n ' + namespace)
|
result, err = deletion.communicate()
|
||||||
deletion = subprocess.Popen(
|
except Exception as ex:
|
||||||
['kubectl', 'delete', kind, name, '--namespace', namespace])
|
print('Error occured during deletion', ex)
|
||||||
result, err = deletion.communicate()
|
|
||||||
else:
|
|
||||||
print('kubectl delete ' + kind + ' ' + name + ' -n ' + namespace)
|
|
||||||
deletion = subprocess.Popen(
|
|
||||||
['kubectl', 'delete', kind, name, '--namespace', namespace])
|
|
||||||
result, err = deletion.communicate()
|
|
||||||
except Exception as ex:
|
|
||||||
print('Error occured during deletion', ex)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
kind = sys.argv[1]
|
|
||||||
name = sys.argv[2]
|
|
||||||
namespace = 'test-' + sys.argv[3]
|
|
||||||
delete(kind, name, namespace)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
|
|||||||
@@ -1,252 +1,33 @@
|
|||||||
from operator import truediv
|
import os, sys, json
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
from unicodedata import name
|
|
||||||
|
|
||||||
# This integration test is used to confirm that k8s resources of a specified name, type, and configuration have been deployed.
|
RESULT = 'false'
|
||||||
# Expected configurations are fed into the python script as command-line arguments and are compared to the configuration of resources that have been deployed.
|
k8_object = None
|
||||||
|
kind = sys.argv[1]
|
||||||
|
name = sys.argv[2]
|
||||||
|
color = sys.argv[3]
|
||||||
|
namespace = 'test-' + sys.argv[4]
|
||||||
|
|
||||||
# args will be formatted like labels=testkey:testValue,otherKey=otherValue
|
print('kubectl get '+kind+' '+name+' -n '+namespace+' -o json')
|
||||||
# or for singular ones, just with containerName=container
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
k8_object = json.load(os.popen('kubectl get '+kind+' '+name+' -n '+namespace+' -o json'))
|
||||||
|
except:
|
||||||
|
sys.exit(kind+' '+name+' not created')
|
||||||
|
|
||||||
kindKey = "kind"
|
try:
|
||||||
nameKey = "name"
|
if kind == 'Deployment' and k8_object['spec']['selector']['matchLabels']['k8s.deploy.color'] == str(color):
|
||||||
containerKey = "containerName"
|
RESULT = 'true'
|
||||||
labelsKey = "labels"
|
if kind == 'Service' and k8_object['spec']['selector']['k8s.deploy.color'] == str(color):
|
||||||
annotationsKey = "annotations"
|
RESULT = 'true'
|
||||||
selectorLabelsKey = "selectorLabels"
|
if kind == 'Ingress':
|
||||||
namespaceKey = "namespace"
|
suffix = ''
|
||||||
ingressServicesKey = "ingressServices"
|
if str(color) == 'green':
|
||||||
tsServicesKey = "tsServices"
|
suffix = '-green'
|
||||||
privateKey = "private"
|
if k8_object['spec']['rules'][0]['http']['paths'][0]['backend']['serviceName']=='nginx-service'+suffix and k8_object['spec']['rules'][0]['http']['paths'][1]['backend']['serviceName']=='unrouted-service':
|
||||||
|
RESULT = 'true'
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if RESULT=='false':
|
||||||
def parseArgs(sysArgs):
|
sys.exit(kind+' '+name+' not labelled properly')
|
||||||
argsDict = stringListToDict(sysArgs, "=")
|
print('Test passed')
|
||||||
|
|
||||||
# mandatory parameters
|
|
||||||
if not kindKey in argsDict:
|
|
||||||
raise ValueError(f"missing key: {kindKey}")
|
|
||||||
|
|
||||||
if not nameKey in argsDict:
|
|
||||||
raise ValueError(f"missing key: {nameKey}")
|
|
||||||
|
|
||||||
if not namespaceKey in argsDict:
|
|
||||||
raise ValueError(f"missing key: {namespaceKey}")
|
|
||||||
|
|
||||||
# reformat map-like parameters (eg, paramName=key1:value1,key2:value2)
|
|
||||||
if labelsKey in argsDict:
|
|
||||||
argsDict[labelsKey] = stringListToDict(
|
|
||||||
argsDict[labelsKey].split(","), ":")
|
|
||||||
|
|
||||||
if annotationsKey in argsDict:
|
|
||||||
argsDict[annotationsKey] = stringListToDict(
|
|
||||||
argsDict[annotationsKey].split(","), ":")
|
|
||||||
|
|
||||||
if selectorLabelsKey in argsDict:
|
|
||||||
argsDict[selectorLabelsKey] = stringListToDict(
|
|
||||||
argsDict[selectorLabelsKey].split(","), ":")
|
|
||||||
|
|
||||||
if tsServicesKey in argsDict:
|
|
||||||
argsDict[tsServicesKey] = stringListToDict(
|
|
||||||
argsDict[tsServicesKey].split(","), ":")
|
|
||||||
|
|
||||||
for key in argsDict[tsServicesKey]:
|
|
||||||
argsDict[tsServicesKey][key] = int(argsDict[tsServicesKey][key])
|
|
||||||
|
|
||||||
# reformat list-like parameters (eg, paramName=value1,value2,value3)
|
|
||||||
if ingressServicesKey in argsDict:
|
|
||||||
argsDict[ingressServicesKey] = argsDict[ingressServicesKey].split(",")
|
|
||||||
|
|
||||||
return argsDict
|
|
||||||
|
|
||||||
|
|
||||||
def stringListToDict(args: list[str], separator: str):
|
|
||||||
parsedArgs = {}
|
|
||||||
for arg in args:
|
|
||||||
print(f"parsing arg {arg}")
|
|
||||||
argSplit = arg.split(separator)
|
|
||||||
parsedArgs[argSplit[0]] = argSplit[1]
|
|
||||||
|
|
||||||
return parsedArgs
|
|
||||||
|
|
||||||
|
|
||||||
def verifyDeployment(deployment, parsedArgs):
|
|
||||||
# test container image, labels, annotations, selector labels
|
|
||||||
if not containerKey in parsedArgs:
|
|
||||||
raise ValueError(
|
|
||||||
f"expected container image name not provided to inspect deployment {parsedArgs[nameKey]}")
|
|
||||||
|
|
||||||
actualImageName = deployment['spec']['template']['spec']['containers'][0]['image']
|
|
||||||
if not actualImageName == parsedArgs[containerKey]:
|
|
||||||
return False, f"expected container image name {parsedArgs[containerKey]} but got {actualImageName} instead"
|
|
||||||
|
|
||||||
if not selectorLabelsKey in parsedArgs:
|
|
||||||
raise ValueError(
|
|
||||||
f"expected selector labels not provided to inspect deployment {parsedArgs[nameKey]}")
|
|
||||||
dictMatch, msg = compareDicts(
|
|
||||||
deployment['spec']['selector']['matchLabels'], parsedArgs[selectorLabelsKey], selectorLabelsKey)
|
|
||||||
if not dictMatch:
|
|
||||||
return dictMatch, msg
|
|
||||||
|
|
||||||
if labelsKey in parsedArgs:
|
|
||||||
dictMatch, msg = compareDicts(
|
|
||||||
deployment['metadata']['labels'], parsedArgs[labelsKey], labelsKey)
|
|
||||||
if not dictMatch:
|
|
||||||
return dictMatch, msg
|
|
||||||
|
|
||||||
if annotationsKey in parsedArgs:
|
|
||||||
dictMatch, msg = compareDicts(
|
|
||||||
deployment['metadata']['annotations'], parsedArgs[annotationsKey], annotationsKey)
|
|
||||||
if not dictMatch:
|
|
||||||
return dictMatch, msg
|
|
||||||
|
|
||||||
return True, ""
|
|
||||||
|
|
||||||
|
|
||||||
def verifyService(service, parsedArgs):
|
|
||||||
# test selector labels, labels, annotations
|
|
||||||
if not selectorLabelsKey in parsedArgs:
|
|
||||||
raise ValueError(
|
|
||||||
f"expected selector labels not provided to inspect service {parsedArgs[nameKey]}")
|
|
||||||
dictMatch, msg = compareDicts(
|
|
||||||
service['spec']['selector'], parsedArgs[selectorLabelsKey], selectorLabelsKey)
|
|
||||||
if not dictMatch:
|
|
||||||
return dictMatch, msg
|
|
||||||
|
|
||||||
if labelsKey in parsedArgs:
|
|
||||||
print(f" service is {service}")
|
|
||||||
dictMatch, msg = compareDicts(
|
|
||||||
service['metadata']['labels'], parsedArgs[labelsKey], labelsKey)
|
|
||||||
if not dictMatch:
|
|
||||||
return dictMatch, msg
|
|
||||||
|
|
||||||
if annotationsKey in parsedArgs:
|
|
||||||
dictMatch, msg = compareDicts(
|
|
||||||
service['metadata']['annotations'], parsedArgs[annotationsKey], annotationsKey)
|
|
||||||
if not dictMatch:
|
|
||||||
return dictMatch, msg
|
|
||||||
|
|
||||||
return True, ""
|
|
||||||
|
|
||||||
|
|
||||||
def verifyIngress(ingress, parsedArgs):
|
|
||||||
# test services in paths
|
|
||||||
if not ingressServicesKey in parsedArgs:
|
|
||||||
raise ValueError(
|
|
||||||
f"expected services not provided to inspect ingress {parsedArgs[nameKey]}")
|
|
||||||
|
|
||||||
expectedIngresses = parsedArgs[ingressServicesKey]
|
|
||||||
for i in range(len(ingress['spec']['rules'][0]['http']['paths'])):
|
|
||||||
print(
|
|
||||||
f"service obj is {ingress['spec']['rules'][0]['http']['paths'][i]}")
|
|
||||||
svcName = ingress['spec']['rules'][0]['http']['paths'][i]['backend']['service']['name']
|
|
||||||
if svcName != expectedIngresses[i]:
|
|
||||||
return False, f"for ingress {parsedArgs[nameKey]} expected svc name {expectedIngresses[i]} at position {i} but got {svcName}"
|
|
||||||
|
|
||||||
return True, ""
|
|
||||||
|
|
||||||
|
|
||||||
def verifyTSObject(tsObj, parsedArgs):
|
|
||||||
if not tsServicesKey in parsedArgs:
|
|
||||||
raise ValueError(
|
|
||||||
f"expected services not provided to inspect ts object {parsedArgs[nameKey]}")
|
|
||||||
|
|
||||||
expectedServices = parsedArgs[tsServicesKey]
|
|
||||||
actualServices = {}
|
|
||||||
backends = tsObj['spec']['backends']
|
|
||||||
for i in range(len(backends)):
|
|
||||||
svcName = backends[i]['service']
|
|
||||||
svcWeight = int(backends[i]['weight'])
|
|
||||||
actualServices[svcName] = svcWeight
|
|
||||||
|
|
||||||
dictResult, msg = compareDicts(
|
|
||||||
actualServices, expectedServices, tsServicesKey)
|
|
||||||
if not dictResult:
|
|
||||||
return False, msg
|
|
||||||
|
|
||||||
return True, ""
|
|
||||||
|
|
||||||
|
|
||||||
def compareDicts(actual: dict, expected: dict, paramName=""):
|
|
||||||
actualKeys = actual.keys()
|
|
||||||
expectedKeys = expected.keys()
|
|
||||||
|
|
||||||
if not actualKeys == expectedKeys:
|
|
||||||
msg = f'dicts had different keys.\n actual: {actual}\n expected: {expected}'
|
|
||||||
if not paramName == "":
|
|
||||||
msg = f"for param {paramName}, " + msg
|
|
||||||
return False, msg
|
|
||||||
for key in actualKeys:
|
|
||||||
if not actual[key] == expected[key]:
|
|
||||||
msg = f'dicts differed at key {key}.\n actual[{key}] is {actual[key]} and expected[{key}] is {expected[key]}'
|
|
||||||
if not paramName == "":
|
|
||||||
msg = f"for param {paramName}, " + msg
|
|
||||||
return False, msg
|
|
||||||
|
|
||||||
return True, ""
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parsedArgs: dict = parseArgs(sys.argv[1:])
|
|
||||||
RESULT = False
|
|
||||||
msg = "unknown type (no verification method currently exists)"
|
|
||||||
k8_object = None
|
|
||||||
|
|
||||||
kind = parsedArgs[kindKey]
|
|
||||||
name = parsedArgs[nameKey]
|
|
||||||
namespace = parsedArgs[namespaceKey]
|
|
||||||
cmd = 'kubectl get '+kind + ' '+name+' -n '+namespace+' -o json'
|
|
||||||
|
|
||||||
k8s_object = None
|
|
||||||
azPrefix = ""
|
|
||||||
try:
|
|
||||||
if privateKey in parsedArgs:
|
|
||||||
uniqueName = parsedArgs[privateKey]
|
|
||||||
azPrefix = f"az aks command invoke --resource-group {uniqueName} --name {uniqueName} --command "
|
|
||||||
cmd = azPrefix + "'" + cmd + "'"
|
|
||||||
outputString = os.popen(cmd).read()
|
|
||||||
successExit = "exitcode=0"
|
|
||||||
if successExit not in outputString:
|
|
||||||
raise ValueError(f"private cluster get failed for {kind} {name}")
|
|
||||||
|
|
||||||
objString = outputString.split(successExit)[1]
|
|
||||||
k8_object = json.loads(objString)
|
|
||||||
|
|
||||||
else:
|
|
||||||
k8_object = json.load(os.popen(cmd))
|
|
||||||
|
|
||||||
if k8_object == None:
|
|
||||||
raise ValueError(f"{kind} {name} was not found")
|
|
||||||
|
|
||||||
except:
|
|
||||||
msg = kind+' '+name+' not created or not found'
|
|
||||||
getAllObjectsCmd = azPrefix + 'kubectl get '+kind+' -n '+namespace
|
|
||||||
if not azPrefix == "":
|
|
||||||
getAllObjectsCmd = azPrefix + "'{getAllObjectsCmd}'" # add extra set of quotes
|
|
||||||
cmd = + "'" + cmd + "'"
|
|
||||||
foundObjects = os.popen().read()
|
|
||||||
suffix = f"resources of type {kind}: {foundObjects}"
|
|
||||||
sys.exit(msg + " " + suffix)
|
|
||||||
|
|
||||||
if kind == 'Deployment':
|
|
||||||
RESULT, msg = verifyDeployment(
|
|
||||||
k8_object, parsedArgs)
|
|
||||||
if kind == 'Service':
|
|
||||||
RESULT, msg = verifyService(
|
|
||||||
k8_object, parsedArgs)
|
|
||||||
if kind == 'Ingress':
|
|
||||||
RESULT, msg = verifyIngress(k8_object, parsedArgs)
|
|
||||||
if kind == "TrafficSplit":
|
|
||||||
RESULT, msg = verifyTSObject(k8_object, parsedArgs)
|
|
||||||
|
|
||||||
if not RESULT:
|
|
||||||
sys.exit(f"{kind} {name} failed check: {msg}")
|
|
||||||
|
|
||||||
print('Test passed')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
|
|||||||
+6
-12
@@ -16,7 +16,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: nginx
|
- name: nginx
|
||||||
image: nginx
|
image: nginx:1.14.2
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
---
|
---
|
||||||
@@ -32,7 +32,7 @@ spec:
|
|||||||
port: 80
|
port: 80
|
||||||
targetPort: 80
|
targetPort: 80
|
||||||
---
|
---
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: nginx-ingress
|
name: nginx-ingress
|
||||||
@@ -43,16 +43,10 @@ spec:
|
|||||||
- http:
|
- http:
|
||||||
paths:
|
paths:
|
||||||
- path: /testpath
|
- path: /testpath
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
backend:
|
||||||
service:
|
serviceName: nginx-service
|
||||||
name: nginx-service
|
servicePort: 80
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
- path: /testpath2
|
- path: /testpath2
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
backend:
|
||||||
service:
|
serviceName: unrouted-service
|
||||||
name: unrouted-service
|
servicePort: 80
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
+1
-1
@@ -16,7 +16,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: nginx
|
- name: nginx
|
||||||
image: nginx
|
image: nginx:1.14.2
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
---
|
---
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: nginx-deployment
|
|
||||||
labels:
|
|
||||||
app: nginx
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: nginx
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: nginx
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: nginx
|
|
||||||
image: nginx
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: nginx-service
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: nginx
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 80
|
|
||||||
targetPort: 80
|
|
||||||
Reference in New Issue
Block a user