Compare commits

...

23 Commits

Author SHA1 Message Date
GitHub Action 4438d05de7 build 2023-12-01 20:50:30 +00:00
David Gamero e10b599478 update version to v prefix (#304) 2023-12-01 15:49:35 -05:00
David Gamero 93550c22f0 add installing ncc to build (#302)
* add installing ncc to build

* include npx to get to bin link
2023-11-06 12:44:42 -05:00
David Gamero 1fea8281df add release worklflow artiact fix (#301) 2023-11-06 12:11:50 -05:00
David Gamero 1b1edcdfc7 bump release workflow sha (#299) 2023-10-31 13:30:03 -04:00
dependabot[bot] 8cbe18c310 Bump decode-uri-component from 0.2.0 to 0.2.2 (#269)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Gamero <david340804@gmail.com>
2023-10-31 12:35:30 -04:00
dependabot[bot] 8efbc8ba92 Bump json5 from 2.2.1 to 2.2.3 (#275)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Gamero <david340804@gmail.com>
2023-10-31 16:24:46 +00:00
dependabot[bot] 699a70732d Bump tough-cookie from 4.0.0 to 4.1.3 (#290)
Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) from 4.0.0 to 4.1.3.
- [Release notes](https://github.com/salesforce/tough-cookie/releases)
- [Changelog](https://github.com/salesforce/tough-cookie/blob/master/CHANGELOG.md)
- [Commits](https://github.com/salesforce/tough-cookie/compare/v4.0.0...v4.1.3)

---
updated-dependencies:
- dependency-name: tough-cookie
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Gamero <david340804@gmail.com>
2023-10-31 12:19:50 -04:00
dependabot[bot] a1d061da9d Bump semver from 5.7.1 to 5.7.2 (#291)
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Gamero <david340804@gmail.com>
2023-10-31 16:14:24 +00:00
dependabot[bot] 7c36b75ebe Bump word-wrap from 1.2.3 to 1.2.4 (#292)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jaiveer Katariya <35347859+jaiveerk@users.noreply.github.com>
2023-10-31 16:06:59 +00:00
dependabot[bot] 2f2901757b Bump @babel/traverse from 7.18.9 to 7.23.2 (#295)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.18.9 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Gamero <david340804@gmail.com>
2023-10-31 12:01:40 -04:00
David Gamero 4aba7c26f3 bump minikube to fix runner deps (#298) 2023-10-31 10:25:13 -04:00
David Gamero d6508445a1 release workflow (#297)
* release workflow

* prettier

* switch to azure repo and sha
2023-10-30 16:33:02 -04:00
Bram de Hart a462095a3c Make annotating resources optional (#287)
* Make annotating resources optional

* Clarify descriptions

* Update README

* Refactor retrieving pods

* Remove annotating resources check in deploy.ts

* Add resource annotation integration test

* Move resource annotation integration test to seperate file

* Lint code

* Remove temporary debugging statements

* Fix integration test name

* Fix test

* Abstracting out repeated logic between verifyDeployment and verifyService

* Fix formattin

* Fix reference

* Fix test

* Refactor test

* Update ubuntu version to latest on canary SMI test

* Update ubuntu version to latest on canary SMI test

* Make annotating resources optional

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Clarify descriptions

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Update README

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Refactor retrieving pods

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Remove annotating resources check in deploy.ts

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Add resource annotation integration test

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Move resource annotation integration test to seperate file

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Lint code

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Remove temporary debugging statements

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Fix integration test name

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Fix test

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Abstracting out repeated logic between verifyDeployment and verifyService

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Fix formattin

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Fix reference

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Fix test

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Refactor test

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

* Update ubuntu version to latest on canary SMI test

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>

---------

Signed-off-by: Bram de Hart <bram.dehart@nsgo.nl>
2023-10-16 14:28:01 +00:00
Olivier Tétard e52890db9e Fix “Service” route-method of the Blue-Green strategy with some manifest files (#283) 2023-04-17 13:52:50 -04:00
David Gamero dd4bbd13a5 bump codeql to node 16 (#281)
* upgrade codeql

* bump codeql init

* name the unit test job

* tats feedback
2023-02-22 16:05:36 -05:00
Oliver King ecb488266d Fixes multiple namespaces bug (#276)
* fix ns bug

* add tests

* rename some variables

* rename ns to namespace

* fix delete + correctly type

* add typing to input obj parser
2023-02-06 13:42:55 -05:00
David Gamero 756cc0a511 upgrade codeql (#279) 2023-01-31 16:43:21 -05:00
Thomas Oddsund dcaec012e2 Check for error from Azure when using the private-cluster feature (#270)
* Check for error from Azure

Move the error check for Azure earlier, so that a well defined error is
thrown on error instead of a JSONSyntax error.

The issue is that when Azure returns an error, like when there's an
issue with the access to the principal used. When this happens, the
stdout field will be an empty string, and the error message will be set.

* Restore check for deserialized exitCode
2023-01-03 10:09:55 -05:00
David Gamero 7dae909398 abstract methods to avoid drift (#273) 2022-12-19 17:59:02 -05:00
Jaiveer Katariya e8a841df59 Fixing Regex Issue + Adding Check for Failures Connecting to Github Repos (#271)
* changed ubuntu runner

* changed minikube action

* Version formatting

* nonedriveR

* update kube version

* installing conntrack'

* updated other actions

* update bg ingress api version

* prettify

* updated ingress backend for new api version

* Added path type

* prettify

* added logging

* added try catch logic to prevent future failures if annotations fail since failing annotations shouldn't affect users

* added nullcheck

* Added fallback filename if workflow fails to get github filepath due to runner issues

* cleanup

* added oliver's feedback + unit test demonstrating regex glitch and fix

* no longer using blank string for failed regex
2022-12-14 08:18:16 -05:00
Oliver King da1e907ad7 Update README.md to v4 (#263) 2022-12-05 18:23:38 -05:00
Jaiveer Katariya 8ce7d1dcdd fixed files to file (#265) 2022-11-29 15:30:17 -05:00
34 changed files with 25161 additions and 450 deletions
+4 -9
View File
@@ -13,20 +13,15 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
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.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@@ -34,7 +29,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -48,4 +43,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2
+9 -8
View File
@@ -1,14 +1,15 @@
name: Create release PR
name: release Project
on:
push:
branches:
- main
paths:
- CHANGELOG.md
workflow_dispatch:
inputs:
release:
description: 'Define release version (ex: v1, v2, v3)'
required: true
jobs:
release-pr:
uses: OliverMKing/javascript-release-workflow/.github/workflows/release-pr.yml@main
release:
uses: Azure/action-release-workflows/.github/workflows/release_js_project.yaml@81e6a8ed41ced9d131dea884ecae7b8c6dc4f799
with:
release: ${{ github.event.inputs.release }}
changelogPath: ./CHANGELOG.md
@@ -38,7 +38,7 @@ jobs:
name: Setup Minikube
uses: medyagh/setup-minikube@latest
with:
minikube-version: 1.24.0
minikube-version: 1.31.2
kubernetes-version: 1.22.3
driver: 'none'
timeout-minutes: 3
@@ -38,7 +38,7 @@ jobs:
name: Setup Minikube
uses: medyagh/setup-minikube@latest
with:
minikube-version: 1.24.0
minikube-version: 1.31.2
kubernetes-version: 1.22.3
driver: 'none'
timeout-minutes: 3
@@ -13,7 +13,7 @@ on:
jobs:
run-integration-test:
name: Run Minikube Integration Tests
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
env:
KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }}
@@ -0,0 +1,89 @@
name: Minikube Integration Tests - resource annotation
on:
pull_request:
branches:
- main
- 'releases/*'
push:
branches:
- main
- 'releases/*'
workflow_dispatch:
jobs:
run-integration-test:
name: Run Minikube Integration Tests
runs-on: ubuntu-latest
env:
KUBECONFIG: /home/runner/.kube/config
NAMESPACE: test-${{ github.run_id }}
steps:
- uses: actions/checkout@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 with resource annotation enabled by default
uses: ./
with:
namespace: ${{ env.NAMESPACE }}
images: nginx:1.14.2
manifests: |
test/integration/manifests/test.yml
action: deploy
- name: Checking if deployments is created with additional resource annotation
run: |
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 labels=app:nginx,workflow:actions.github.com-k8s-deploy,workflowFriendlyName:Minikube_Integration_Tests_-_resource_annotation selectorLabels=app:nginx annotations=actions.github.com/k8s-deploy,deployment.kubernetes.io/revision,kubectl.kubernetes.io/last-applied-configuration
- name: Cleaning previously created deployment
run: |
python test/integration/k8s-deploy-delete.py 'Deployment' 'all' ${{ env.NAMESPACE }}
- name: Executing deploy action for pod with resource annotation disabled
uses: ./
with:
namespace: ${{ env.NAMESPACE }}
images: nginx:1.14.2
manifests: |
test/integration/manifests/test.yml
action: deploy
annotate-resources: false
- name: Checking if deployment is created without additional resource annotation
run: |
python test/integration/k8s-deploy-test.py namespace=${{ env.NAMESPACE }} kind=Deployment name=nginx-deployment containerName=nginx:1.14.2 selectorLabels=app:nginx annotations=deployment.kubernetes.io/revision,kubectl.kubernetes.io/last-applied-configuration
+2 -1
View File
@@ -11,9 +11,10 @@ on: # rebuild any PRs and main branch changes
jobs:
build: # make sure build/ci works properly
name: Run Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- run: |
npm install
npm test
-1
View File
@@ -2,6 +2,5 @@ node_modules
.DS_Store
.idea
lib/
coverage/
+11
View File
@@ -0,0 +1,11 @@
# Changelog
## [v4.10.0] - 2023-10-30
### Added
- #287 Make annotating resources optional
- #283 Fix “Service” route-method of the Blue-Green strategy with some manifest files
- #281 bump codeql to node 16
- #279 upgrade codeql
- #276 Fixes multiple namespaces bug
+14 -10
View File
@@ -113,9 +113,13 @@ Following are the key capabilities of this action:
<td>force </br></br>(Optional)</td>
<td>Deploy when a previous deployment already exists. If true then '--force' argument is added to the apply command. Using '--force' argument is not recommended in production.</td>
</tr>
<tr>
<td>annotate-resources</br></br>(Optional)</td>
<td>Acceptable values: true/false</br>Default value: true</br>Switch whether to annotate the resources or not. If set to false all annotations are skipped completely.</td>
</tr>
<tr>
<td>annotate-namespace</br></br>(Optional)</td>
<td>Acceptable values: true/false</br>Default value: true</br>Switch whether to annotate the namespace resources object or not</td>
<td>Acceptable values: true/false</br>Default value: true</br>Switch whether to annotate the namespace resources object or not. Ignored when annotate-resources is set to false.</td>
</tr>
<tr>
<td>skip-tls-verify</br></br>(Optional)</td>
@@ -128,7 +132,7 @@ Following are the key capabilities of this action:
### Basic deployment (without any deployment strategy)
```yaml
- uses: Azure/k8s-deploy@v3.1
- uses: Azure/k8s-deploy@v4
with:
namespace: 'myapp'
manifests: |
@@ -162,7 +166,7 @@ Following are the key capabilities of this action:
### Canary deployment without service mesh
```yaml
- uses: Azure/k8s-deploy@v3.1
- uses: Azure/k8s-deploy@v4
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
@@ -181,7 +185,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:
```yaml
- uses: Azure/k8s-deploy@v3.1
- uses: Azure/k8s-deploy@v4
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
@@ -199,7 +203,7 @@ To promote/reject the canary created by the above snippet, the following YAML sn
### Canary deployment based on Service Mesh Interface
```yaml
- uses: Azure/k8s-deploy@v3.1
- uses: Azure/k8s-deploy@v4
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
@@ -220,7 +224,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:
```yaml
- uses: Azure/k8s-deploy@v3.1
- uses: Azure/k8s-deploy@v4
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }} '
@@ -239,7 +243,7 @@ To promote/reject the canary created by the above snippet, the following YAML sn
### Blue-Green deployment with different route methods
```yaml
- uses: Azure/k8s-deploy@v3.1
- uses: Azure/k8s-deploy@v4
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
@@ -259,7 +263,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:
```yaml
- uses: Azure/k8s-deploy@v3.1
- uses: Azure/k8s-deploy@v4
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
@@ -316,7 +320,7 @@ jobs:
container-registry-password: ${{ secrets.REGISTRY_PASSWORD }}
secret-name: demo-k8s-secret
- uses: Azure/k8s-deploy@v3.1
- uses: Azure/k8s-deploy@v4
with:
action: deploy
manifests: |
@@ -362,7 +366,7 @@ jobs:
container-registry-password: ${{ secrets.REGISTRY_PASSWORD }}
secret-name: demo-k8s-secret
- uses: Azure/k8s-deploy@v3.1
- uses: Azure/k8s-deploy@v4
with:
action: deploy
manifests: |
+5 -1
View File
@@ -59,8 +59,12 @@ inputs:
description: 'Github token'
default: ${{ github.token }}
required: true
annotate-resources:
description: 'Annotate the resources. If set to false all annotations are skipped completely.'
required: false
default: true
annotate-namespace:
description: 'Annotate the target namespace'
description: 'Annotate the target namespace. Ignored when annotate-resources is set to false.'
required: false
default: true
private-cluster:
+24128
View File
File diff suppressed because one or more lines are too long
+422 -252
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -4,7 +4,7 @@
"author": "Deepak Sattiraju",
"license": "MIT",
"scripts": {
"build": "npx ncc build src/run.ts -o lib",
"build": "npm i ncc && npx ncc build src/run.ts -o lib",
"test": "jest",
"coverage": "jest --coverage=true",
"format": "prettier --write .",
@@ -18,14 +18,15 @@
"@octokit/core": "^3.5.1",
"@octokit/plugin-retry": "^3.0.9",
"@types/minipass": "^3.1.2",
"js-yaml": "3.13.1"
"js-yaml": "3.13.1",
"ncc": "^0.3.6"
},
"devDependencies": {
"@types/jest": "^26.0.0",
"@types/js-yaml": "^3.12.7",
"@types/node": "^12.20.41",
"@vercel/ncc": "^0.36.1",
"jest": "^26.0.0",
"ncc": "^0.3.6",
"prettier": "^2.7.1",
"ts-jest": "^26.0.0",
"typescript": "3.9.5"
+4 -9
View File
@@ -56,24 +56,19 @@ export async function deploy(
for (const ingressResource of ingressResources) {
await kubectl.getResource(
KubernetesConstants.DiscoveryAndLoadBalancerResource.INGRESS,
ingressResource.name
ingressResource.name,
false,
ingressResource.namespace
)
}
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,
resourceTypes,
allPods
resourceTypes
)
core.endGroup()
}
+2 -19
View File
@@ -129,19 +129,13 @@ async function promoteCanary(kubectl: Kubectl, manifests: string[]) {
// 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)
await annotateAndLabelResources(filesToAnnotate, kubectl, resources)
core.endGroup()
}
@@ -219,17 +213,6 @@ async function promoteBlueGreen(kubectl: Kubectl, manifests: string[]) {
// 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
)
await annotateAndLabelResources(deployedManifestFiles, kubectl, resources)
core.endGroup()
}
@@ -38,7 +38,8 @@ export async function deleteGreenObjects(
const resourcesToDelete: K8sDeleteObject[] = toDelete.map((obj) => {
return {
name: getBlueGreenResourceName(obj.metadata.name, GREEN_SUFFIX),
kind: obj.kind
kind: obj.kind,
namespace: obj.metadata.namespace
}
})
@@ -66,31 +67,25 @@ export async function deleteObjects(
// other common functions
export function getManifestObjects(filePaths: string[]): BlueGreenManifests {
const deploymentEntityList: K8sObject[] = []
const serviceEntityList: K8sObject[] = []
const routedServiceEntityList: K8sObject[] = []
const unroutedServiceEntityList: K8sObject[] = []
const ingressEntityList: K8sObject[] = []
const otherEntitiesList: K8sObject[] = []
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) => {
const fileContents = fs.readFileSync(filePath).toString()
yaml.safeLoadAll(fileContents, (inputObject) => {
if (!!inputObject) {
const kind = inputObject.kind
const name = inputObject.metadata.name
if (isDeploymentEntity(kind)) {
deploymentEntityList.push(inputObject)
} else if (isServiceEntity(kind)) {
if (isServiceRouted(inputObject, deploymentEntityList)) {
routedServiceEntityList.push(inputObject)
serviceNameMap.set(
name,
getBlueGreenResourceName(name, GREEN_SUFFIX)
)
} else {
unroutedServiceEntityList.push(inputObject)
}
serviceEntityList.push(inputObject)
} else if (isIngressEntity(kind)) {
ingressEntityList.push(inputObject)
} else {
@@ -100,6 +95,16 @@ 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 {
serviceEntityList: routedServiceEntityList,
serviceNameMap: serviceNameMap,
@@ -234,9 +239,10 @@ export function isServiceSelectorSubsetOfMatchLabel(
export async function fetchResource(
kubectl: Kubectl,
kind: string,
name: string
name: string,
namespace?: string
): Promise<K8sObject> {
const result = await kubectl.getResource(kind, name)
const result = await kubectl.getResource(kind, name, false, namespace)
if (result == null || !!result.stderr) {
return null
}
@@ -97,7 +97,8 @@ export async function validateIngresses(
const existingIngress = await fetchResource(
kubectl,
inputObject.kind,
inputObject.metadata.name
inputObject.metadata.name,
inputObject?.metadata?.namespace
)
const isValid =
@@ -31,7 +31,8 @@ export async function validateServicesState(
const existingService = await fetchResource(
kubectl,
serviceObject.kind,
serviceObject.metadata.name
serviceObject.metadata.name,
serviceObject?.metadata?.namespace
)
let isServiceGreen =
@@ -142,7 +142,8 @@ export async function validateTrafficSplitsState(
let trafficSplitObject = await fetchResource(
kubectl,
TRAFFIC_SPLIT_OBJECT,
getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX)
getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX),
serviceObject?.metadata?.namespace
)
core.debug(
`ts object extracted was ${JSON.stringify(trafficSplitObject)}`
@@ -183,7 +184,8 @@ export async function cleanupSMI(
serviceObject.metadata.name,
GREEN_SUFFIX
),
kind: serviceObject.kind
kind: serviceObject.kind,
namespace: serviceObject?.metadata?.namespace
})
})
+9 -4
View File
@@ -195,9 +195,13 @@ async function cleanUpCanary(
files: string[],
includeServices: boolean
): Promise<string[]> {
const deleteObject = async function (kind, name) {
const deleteObject = async function (
kind: string,
name: string,
namespace: string | undefined
) {
try {
const result = await kubectl.delete([kind, name])
const result = await kubectl.delete([kind, name], namespace)
checkForErrors([result])
} catch (ex) {
// Ignore failures of delete if it doesn't exist
@@ -213,6 +217,7 @@ async function cleanUpCanary(
for (const inputObject of parsedYaml) {
const name = inputObject.metadata.name
const kind = inputObject.kind
const namespace: string | undefined = inputObject?.metadata?.namespace
if (
isDeploymentEntity(kind) ||
@@ -222,8 +227,8 @@ async function cleanUpCanary(
const canaryObjectName = getCanaryResourceName(name)
const baselineObjectName = getBaselineResourceName(name)
await deleteObject(kind, canaryObjectName)
await deleteObject(kind, baselineObjectName)
await deleteObject(kind, canaryObjectName, namespace)
await deleteObject(kind, baselineObjectName, namespace)
}
}
}
+38 -18
View File
@@ -147,32 +147,45 @@ export async function checkManifestStability(
export async function annotateAndLabelResources(
files: string[],
kubectl: Kubectl,
resourceTypes: Resource[],
allPods: any
resourceTypes: Resource[]
) {
const defaultWorkflowFileName = 'k8s-deploy-failed-workflow-annotation'
const githubToken = core.getInput('token')
const workflowFilePath = await getWorkflowFilePath(githubToken)
let workflowFilePath
try {
workflowFilePath = await getWorkflowFilePath(githubToken)
} catch (ex) {
core.warning(`Failed to extract workflow file name: ${ex}`)
workflowFilePath = defaultWorkflowFileName
}
const deploymentConfig = await getDeploymentConfig()
const annotationKeyLabel = getWorkflowAnnotationKeyLabel()
await annotateResources(
files,
kubectl,
resourceTypes,
allPods,
annotationKeyLabel,
workflowFilePath,
deploymentConfig
const shouldAnnotateResources = !(
core.getInput('annotate-resources').toLowerCase() === 'false'
)
if (shouldAnnotateResources) {
await annotateResources(
files,
kubectl,
resourceTypes,
annotationKeyLabel,
workflowFilePath,
deploymentConfig
).catch((err) => core.warning(`Failed to annotate resources: ${err} `))
}
await labelResources(files, kubectl, annotationKeyLabel).catch((err) =>
core.warning(`Failed to label resources: ${err}`)
)
await labelResources(files, kubectl, annotationKeyLabel)
}
async function annotateResources(
files: string[],
kubectl: Kubectl,
resourceTypes: Resource[],
allPods: any,
annotationKey: string,
workflowFilePath: string,
deploymentConfig: DeploymentConfig
@@ -208,14 +221,21 @@ async function annotateResources(
)
if (annotateNamespace) {
annotateResults.push(
await kubectl.annotate('namespace', namespace, annotationKeyValStr)
await kubectl.annotate(
'namespace',
namespace,
annotationKeyValStr,
namespace
)
)
}
for (const file of files) {
try {
const annotateResult = await kubectl.annotateFiles(
file,
annotationKeyValStr
annotationKeyValStr,
namespace
)
annotateResults.push(annotateResult)
} catch (e) {
@@ -233,8 +253,8 @@ async function annotateResources(
kubectl,
resource.type,
resource.name,
annotationKeyValStr,
allPods
resource.namespace,
annotationKeyValStr
)
).forEach((execResult) => annotateResults.push(execResult))
}
@@ -258,7 +278,7 @@ async function labelResources(
const labelResults = []
for (const file of files) {
try {
const labelResult = await kubectl.labelFiles(files, labels)
const labelResult = await kubectl.labelFiles(file, labels)
labelResults.push(labelResult)
} catch (e) {
core.warning(`failed to annotate resource: ${e}`)
+2
View File
@@ -2,6 +2,7 @@ export interface K8sObject {
metadata: {
name: string
labels: Map<string, string>
namespace?: string
}
kind: string
spec: any
@@ -16,6 +17,7 @@ export interface K8sServiceObject extends K8sObject {
export interface K8sDeleteObject {
name: string
kind: string
namespace?: string
}
export interface K8sIngress extends K8sObject {
+176 -1
View File
@@ -3,7 +3,6 @@ import * as exec from '@actions/exec'
import * as io from '@actions/io'
import * as core from '@actions/core'
import * as toolCache from '@actions/tool-cache'
import {config} from 'process'
describe('Kubectl path', () => {
const version = '1.1'
@@ -38,6 +37,7 @@ describe('Kubectl path', () => {
const kubectlPath = 'kubectlPath'
const testNamespace = 'testNamespace'
const defaultNamespace = 'default'
const otherNamespace = 'otherns'
describe('Kubectl class', () => {
describe('default namespace behavior', () => {
const kubectl = new Kubectl(kubectlPath, defaultNamespace)
@@ -122,6 +122,26 @@ describe('Kubectl class', () => {
],
{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 () => {
@@ -140,6 +160,26 @@ describe('Kubectl class', () => {
],
{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 () => {
@@ -165,6 +205,27 @@ describe('Kubectl class', () => {
],
{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 () => {
@@ -185,6 +246,22 @@ describe('Kubectl class', () => {
],
{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 () => {
@@ -205,6 +282,22 @@ describe('Kubectl class', () => {
],
{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 () => {
@@ -225,6 +318,21 @@ describe('Kubectl class', () => {
],
{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 () => {
@@ -245,6 +353,21 @@ describe('Kubectl class', () => {
],
{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 () => {
@@ -273,6 +396,20 @@ describe('Kubectl class', () => {
],
{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 () => {
@@ -291,6 +428,22 @@ describe('Kubectl class', () => {
],
{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 () => {
@@ -321,6 +474,14 @@ describe('Kubectl class', () => {
['delete', arg, '--namespace', testNamespace],
{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 () => {
@@ -331,6 +492,14 @@ describe('Kubectl class', () => {
['delete', ...args, '--namespace', testNamespace],
{silent: false}
)
// override ns
await kubectl.delete(args, otherNamespace)
expect(exec.getExecOutput).toBeCalledWith(
kubectlPath,
['delete', ...args, '--namespace', otherNamespace],
{silent: false}
)
})
})
@@ -369,5 +538,11 @@ describe('Kubectl class', () => {
[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
})
})
})
+60 -34
View File
@@ -3,11 +3,11 @@ import {createInlineArray} from '../utilities/arrayUtils'
import * as core from '@actions/core'
import * as toolCache from '@actions/tool-cache'
import * as io from '@actions/io'
import {exec} from 'child_process'
export interface Resource {
name: string
type: string
namespace?: string
}
export class Kubectl {
@@ -20,7 +20,7 @@ export class Kubectl {
constructor(
kubectlPath: string,
namespace: string = 'default',
namespace: string = '',
ignoreSSLErrors: boolean = false,
resourceGroup: string = '',
name: string = ''
@@ -47,7 +47,7 @@ export class Kubectl {
]
if (force) applyArgs.push('--force')
return await this.execute(applyArgs)
return await this.execute(applyArgs.concat(this.getFlags()))
} catch (err) {
core.debug('Kubectl apply failed:' + err)
}
@@ -56,27 +56,43 @@ export class Kubectl {
public async describe(
resourceType: string,
resourceName: string,
silent: boolean = false
silent: boolean = false,
namespace?: string
): Promise<ExecOutput> {
return await this.execute(
['describe', resourceType, resourceName],
['describe', resourceType, resourceName].concat(
this.getFlags(namespace)
),
silent
)
}
public async getNewReplicaSet(deployment: string) {
const result = await this.describe('deployment', deployment, true)
public async getNewReplicaSet(deployment: string, namespace?: string) {
const result = await this.describe(
'deployment',
deployment,
true,
namespace
)
let newReplicaSet = ''
if (result?.stdout) {
const stdout = result.stdout.split('\n')
core.debug('stdout from getNewReplicaSet is ' + JSON.stringify(stdout))
stdout.forEach((line: string) => {
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
.substring(newreplicaset.length)
.trim()
.split(' ')[0]
}
})
}
@@ -86,7 +102,8 @@ export class Kubectl {
public async annotate(
resourceType: string,
resourceName: string,
annotation: string
annotation: string,
namespace?: string
): Promise<ExecOutput> {
const args = [
'annotate',
@@ -94,13 +111,14 @@ export class Kubectl {
resourceName,
annotation,
'--overwrite'
]
].concat(this.getFlags(namespace))
return await this.execute(args)
}
public async annotateFiles(
files: string | string[],
annotation: string
annotation: string,
namespace?: string
): Promise<ExecOutput> {
const filesToAnnotate = createInlineArray(files)
core.debug(`annotating ${filesToAnnotate} with annotation ${annotation}`)
@@ -110,16 +128,14 @@ export class Kubectl {
filesToAnnotate,
annotation,
'--overwrite'
]
core.debug(
`sending args from annotate to execute: ${JSON.stringify(args)}`
)
].concat(this.getFlags(namespace))
return await this.execute(args)
}
public async labelFiles(
files: string | string[],
labels: string[]
labels: string[],
namespace?: string
): Promise<ExecOutput> {
const args = [
'label',
@@ -127,51 +143,59 @@ export class Kubectl {
createInlineArray(files),
...labels,
'--overwrite'
]
].concat(this.getFlags(namespace))
return await this.execute(args)
}
public async getAllPods(): Promise<ExecOutput> {
return await this.execute(['get', 'pods', '-o', 'json'], true)
return await this.execute(
['get', 'pods', '-o', 'json'].concat(this.getFlags()),
true
)
}
public async checkRolloutStatus(
resourceType: string,
name: string
name: string,
namespace?: string
): Promise<ExecOutput> {
return await this.execute([
'rollout',
'status',
`${resourceType}/${name}`
])
return await this.execute(
['rollout', 'status', `${resourceType}/${name}`].concat(
this.getFlags(namespace)
)
)
}
public async getResource(
resourceType: string,
name: string,
silentFailure: boolean = false
silentFailure: boolean = false,
namespace?: string
): Promise<ExecOutput> {
core.debug(
'fetching resource of type ' + resourceType + ' and name ' + name
)
return await this.execute(
['get', `${resourceType}/${name}`, '-o', 'json'],
['get', `${resourceType}/${name}`, '-o', 'json'].concat(
this.getFlags(namespace)
),
silentFailure
)
}
public executeCommand(command: string, args?: string) {
if (!command) throw new Error('Command must be defined')
return args ? this.execute([command, args]) : this.execute([command])
const a = args ? [args] : []
return this.execute([command, ...a.concat(this.getFlags())])
}
public delete(args: string | string[]) {
if (typeof args === 'string') return this.execute(['delete', args])
return this.execute(['delete', ...args])
public delete(args: string | string[], namespace?: string) {
if (typeof args === 'string')
return this.execute(['delete', args].concat(this.getFlags(namespace)))
return this.execute(['delete', ...args.concat(this.getFlags(namespace))])
}
protected async execute(args: string[], silent: boolean = false) {
args = args.concat(this.getExecuteFlags())
core.debug(`Kubectl run with command: ${this.kubectlPath} ${args}`)
return await getExecOutput(this.kubectlPath, args, {
@@ -179,13 +203,15 @@ export class Kubectl {
})
}
protected getExecuteFlags(): string[] {
protected getFlags(namespaceOverride?: string): string[] {
const flags = []
if (this.ignoreSSLErrors) {
flags.push('--insecure-skip-tls-verify')
}
if (this.namespace) {
flags.push('--namespace', this.namespace)
const ns = namespaceOverride || this.namespace
if (ns) {
flags.push('--namespace', ns)
}
return flags
+21 -1
View File
@@ -1,12 +1,32 @@
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('')
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}`
)
)
})
})
+8 -3
View File
@@ -8,8 +8,6 @@ import * as path from 'path'
export class PrivateKubectl extends Kubectl {
protected async execute(args: string[], silent: boolean = false) {
args = args.concat(this.getExecuteFlags())
args.unshift('kubectl')
let kubectlCmd = args.join(' ')
let addFileFlag = false
@@ -75,11 +73,18 @@ export class PrivateKubectl extends Kubectl {
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 (runOutput.exitCode !== 0 && runObj.exitCode !== 0) {
if (runObj.exitCode !== 0) {
throw Error(`failed private cluster Kubectl command: ${kubectlCmd}`)
}
+21 -5
View File
@@ -2,6 +2,8 @@ import * as core from '@actions/core'
import {ExecOutput} from '@actions/exec'
import {Kubectl} from '../types/kubectl'
const NAMESPACE = 'namespace'
export function checkForErrors(
execResults: ExecOutput[],
warnIfError?: boolean
@@ -30,7 +32,12 @@ export async function getLastSuccessfulRunSha(
annotationKey: string
): Promise<string> {
try {
const result = await kubectl.getResource('namespace', namespaceName)
const result = await kubectl.getResource(
NAMESPACE,
namespaceName,
false,
namespaceName
)
if (result?.stderr) {
core.warning(result.stderr)
return process.env.GITHUB_SHA
@@ -53,15 +60,23 @@ export async function annotateChildPods(
kubectl: Kubectl,
resourceType: string,
resourceName: string,
annotationKeyValStr: string,
allPods
namespace: string | undefined,
annotationKeyValStr: string
): Promise<ExecOutput[]> {
let owner = resourceName
if (resourceType.toLowerCase().indexOf('deployment') > -1) {
owner = await kubectl.getNewReplicaSet(resourceName)
owner = await kubectl.getNewReplicaSet(resourceName, namespace)
}
const commandExecutionResults = []
let allPods
try {
allPods = JSON.parse((await kubectl.getAllPods()).stdout)
} catch (e) {
core.debug(`Unable to parse pods: ${e}`)
}
if (allPods?.items && allPods.items?.length > 0) {
allPods.items.forEach((pod) => {
const owners = pod?.metadata?.ownerReferences
@@ -72,7 +87,8 @@ export async function annotateChildPods(
kubectl.annotate(
'pod',
pod.metadata.name,
annotationKeyValStr
annotationKeyValStr,
namespace
)
)
break
+51 -28
View File
@@ -4,6 +4,9 @@ import {Kubectl, Resource} from '../types/kubectl'
import {checkForErrors} from './kubectlUtils'
import {sleep} from './timeUtils'
const IS_SILENT = false
const POD = 'pod'
export async function checkManifestStability(
kubectl: Kubectl,
resources: Resource[]
@@ -20,24 +23,35 @@ export async function checkManifestStability(
try {
const result = await kubectl.checkRolloutStatus(
resource.type,
resource.name
resource.name,
resource.namespace
)
checkForErrors([result])
} catch (ex) {
core.error(ex)
await kubectl.describe(resource.type, resource.name)
await kubectl.describe(
resource.type,
resource.name,
IS_SILENT,
resource.namespace
)
rolloutStatusHasErrors = true
}
}
if (resource.type == KubernetesConstants.KubernetesWorkload.POD) {
try {
await checkPodStatus(kubectl, resource.name)
await checkPodStatus(kubectl, resource)
} catch (ex) {
core.warning(
`Could not determine pod status: ${JSON.stringify(ex)}`
)
await kubectl.describe(resource.type, resource.name)
await kubectl.describe(
resource.type,
resource.name,
IS_SILENT,
resource.namespace
)
}
}
if (
@@ -45,14 +59,11 @@ export async function checkManifestStability(
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE
) {
try {
const service = await getService(kubectl, resource.name)
const service = await getService(kubectl, resource)
const {spec, status} = service
if (spec.type === KubernetesConstants.ServiceTypes.LOAD_BALANCER) {
if (!isLoadBalancerIPAssigned(status)) {
await waitForServiceExternalIPAssignment(
kubectl,
resource.name
)
await waitForServiceExternalIPAssignment(kubectl, resource)
} else {
core.info(
`ServiceExternalIP ${resource.name} ${status.loadBalancer.ingress[0].ip}`
@@ -63,7 +74,12 @@ export async function checkManifestStability(
core.warning(
`Could not determine service status of: ${resource.name} Error: ${ex}`
)
await kubectl.describe(resource.type, resource.name)
await kubectl.describe(
resource.type,
resource.name,
IS_SILENT,
resource.namespace
)
}
}
}
@@ -75,7 +91,7 @@ export async function checkManifestStability(
export async function checkPodStatus(
kubectl: Kubectl,
podName: string
pod: Resource
): Promise<void> {
const sleepTimeout = 10 * 1000 // 10 seconds
const iterations = 60 // 60 * 10 seconds timeout = 10 minutes max timeout
@@ -85,8 +101,8 @@ export async function checkPodStatus(
for (let i = 0; i < iterations; i++) {
await sleep(sleepTimeout)
core.debug(`Polling for pod status: ${podName}`)
podStatus = await getPodStatus(kubectl, podName)
core.debug(`Polling for pod status: ${pod.name}`)
podStatus = await getPodStatus(kubectl, pod)
if (
podStatus &&
@@ -97,37 +113,42 @@ export async function checkPodStatus(
}
}
podStatus = await getPodStatus(kubectl, podName)
podStatus = await getPodStatus(kubectl, pod)
switch (podStatus.phase) {
case 'Succeeded':
case 'Running':
if (isPodReady(podStatus)) {
console.log(`pod/${podName} is successfully rolled out`)
console.log(`pod/${pod.name} is successfully rolled out`)
} else {
kubectlDescribeNeeded = true
}
break
case 'Pending':
if (!isPodReady(podStatus)) {
core.warning(`pod/${podName} rollout status check timed out`)
core.warning(`pod/${pod.name} rollout status check timed out`)
kubectlDescribeNeeded = true
}
break
case 'Failed':
core.error(`pod/${podName} rollout failed`)
core.error(`pod/${pod.name} rollout failed`)
kubectlDescribeNeeded = true
break
default:
core.warning(`pod/${podName} rollout status: ${podStatus.phase}`)
core.warning(`pod/${pod.name} rollout status: ${podStatus.phase}`)
}
if (kubectlDescribeNeeded) {
await kubectl.describe('pod', podName)
await kubectl.describe(POD, pod.name, IS_SILENT, pod.namespace)
}
}
async function getPodStatus(kubectl: Kubectl, podName: string) {
const podResult = await kubectl.getResource('pod', podName)
async function getPodStatus(kubectl: Kubectl, pod: Resource) {
const podResult = await kubectl.getResource(
POD,
pod.name,
IS_SILENT,
pod.namespace
)
checkForErrors([podResult])
return JSON.parse(podResult.stdout).status
@@ -151,10 +172,12 @@ function isPodReady(podStatus: any): boolean {
return allContainersAreReady
}
async function getService(kubectl: Kubectl, serviceName) {
async function getService(kubectl: Kubectl, service: Resource) {
const serviceResult = await kubectl.getResource(
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE,
serviceName
service.name,
IS_SILENT,
service.namespace
)
checkForErrors([serviceResult])
@@ -163,25 +186,25 @@ async function getService(kubectl: Kubectl, serviceName) {
async function waitForServiceExternalIPAssignment(
kubectl: Kubectl,
serviceName: string
service: Resource
): Promise<void> {
const sleepTimeout = 10 * 1000 // 10 seconds
const iterations = 18 // 18 * 10 seconds timeout = 3 minutes max timeout
for (let i = 0; i < iterations; i++) {
core.info(`Wait for service ip assignment : ${serviceName}`)
core.info(`Wait for service ip assignment : ${service.name}`)
await sleep(sleepTimeout)
const status = (await getService(kubectl, serviceName)).status
const status = (await getService(kubectl, service)).status
if (isLoadBalancerIPAssigned(status)) {
core.info(
`ServiceExternalIP ${serviceName} ${status.loadBalancer.ingress[0].ip}`
`ServiceExternalIP ${service.name} ${status.loadBalancer.ingress[0].ip}`
)
return
}
}
core.warning(`Wait for service ip assignment timed out${serviceName}`)
core.warning(`Wait for service ip assignment timed out ${service.name}`)
}
function isLoadBalancerIPAssigned(status: any) {
+2 -1
View File
@@ -280,7 +280,8 @@ export function getResources(
) {
resources.push({
type: inputObject.kind,
name: inputObject.metadata.name
name: inputObject.metadata.name,
namespace: inputObject?.metadata?.namespace
})
}
})
+14 -1
View File
@@ -1,4 +1,8 @@
import {cleanLabel} from '../utilities/workflowAnnotationUtils'
import {
cleanLabel,
removeInvalidLabelCharacters,
VALID_LABEL_REGEX
} from '../utilities/workflowAnnotationUtils'
describe('WorkflowAnnotationUtils', () => {
describe('cleanLabel', () => {
@@ -16,5 +20,14 @@ describe('WorkflowAnnotationUtils', () => {
cleanLabel('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)
})
})
})
+12 -4
View File
@@ -2,6 +2,8 @@ import {DeploymentConfig} from '../types/deploymentConfig'
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(
lastSuccessRunSha: string,
workflowFilePath: string,
@@ -37,11 +39,17 @@ export function getWorkflowAnnotationKeyLabel(): string {
* @returns cleaned label
*/
export function cleanLabel(label: string): string {
let removedInvalidChars = label
let removedInvalidChars = removeInvalidLabelCharacters(label)
const regexResult = VALID_LABEL_REGEX.exec(removedInvalidChars) || [
'github-workflow-file'
]
return regexResult[0]
}
export function removeInvalidLabelCharacters(label: string): string {
return label
.replace(/\s/gi, '_')
.replace(/[\/\\\|]/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] || ''
}
+2 -2
View File
@@ -7,7 +7,7 @@ def delete(kind, name, namespace):
if (name == "all"):
print('kubectl delete --all' + kind + ' -n ' + namespace)
deletion = subprocess.Popen(
['kubectl', 'delete', kind, name, '--namespace', namespace])
['kubectl', 'delete', kind, '--all', '--namespace', namespace])
result, err = deletion.communicate()
else:
print('kubectl delete ' + kind + ' ' + name + ' -n ' + namespace)
@@ -21,7 +21,7 @@ def delete(kind, name, namespace):
def main():
kind = sys.argv[1]
name = sys.argv[2]
namespace = 'test-' + sys.argv[3]
namespace = sys.argv[3]
delete(kind, name, namespace)
+21 -15
View File
@@ -41,10 +41,6 @@ def parseArgs(sysArgs):
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(","), ":")
@@ -60,6 +56,9 @@ def parseArgs(sysArgs):
if ingressServicesKey in argsDict:
argsDict[ingressServicesKey] = argsDict[ingressServicesKey].split(",")
if annotationsKey in argsDict:
argsDict[annotationsKey] = argsDict[annotationsKey].split(",")
return argsDict
@@ -98,14 +97,14 @@ def verifyDeployment(deployment, parsedArgs):
return dictMatch, msg
if annotationsKey in parsedArgs:
dictMatch, msg = compareDicts(
deployment['metadata']['annotations'], parsedArgs[annotationsKey], annotationsKey)
if not dictMatch:
return dictMatch, msg
if len(parsedArgs[annotationsKey]) != len(deployment['metadata']['annotations']):
return False, f"expected {len(parsedArgs[annotationsKey])} annotations but found {len(deployment['metadata']['annotations'])}"
keysPresent, msg = validateKeyPresence(
deployment['metadata']['annotations'], parsedArgs[annotationsKey])
if not keysPresent:
return keysPresent, msg
return True, ""
def verifyService(service, parsedArgs):
# test selector labels, labels, annotations
if not selectorLabelsKey in parsedArgs:
@@ -124,10 +123,10 @@ def verifyService(service, parsedArgs):
return dictMatch, msg
if annotationsKey in parsedArgs:
dictMatch, msg = compareDicts(
service['metadata']['annotations'], parsedArgs[annotationsKey], annotationsKey)
if not dictMatch:
return dictMatch, msg
keysPresent, msg = validateKeyPresence(
service['metadata']['annotations'], parsedArgs[annotationsKey])
if not keysPresent:
return keysPresent, msg
return True, ""
@@ -188,6 +187,13 @@ def compareDicts(actual: dict, expected: dict, paramName=""):
return True, ""
def validateKeyPresence(actualDict: dict, expectedKeys: list):
actualKeys = actualDict.keys()
for key in expectedKeys:
if key not in actualKeys:
return False, f"expected key {key} not found in actual dict. \n actual dict keys {','.join(actualKeys)}"
return True, ""
def main():
parsedArgs: dict = parseArgs(sys.argv[1:])
@@ -220,7 +226,7 @@ def main():
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