Compare commits

..

1 Commits

Author SHA1 Message Date
Oliver King 42f5b49202 Update README.md to v4 2022-11-28 13:46:16 -05:00
23 changed files with 322 additions and 24532 deletions
+9 -4
View File
@@ -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 -2
View File
@@ -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
+1
View File
@@ -2,5 +2,6 @@ node_modules
.DS_Store .DS_Store
.idea .idea
lib/
coverage/ coverage/
-24092
View File
File diff suppressed because one or more lines are too long
+208 -16
View File
@@ -22,8 +22,8 @@
"@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",
"ncc": "^0.3.6",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"ts-jest": "^26.0.0", "ts-jest": "^26.0.0",
"typescript": "3.9.5" "typescript": "3.9.5"
@@ -1183,15 +1183,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",
@@ -1884,6 +1875,15 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "dev": true
}, },
"node_modules/colors": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.2.3.tgz",
"integrity": "sha512-qTfM2pNFeMZcLvf/RbrVAzDEVttZjFhaApfx9dplNjvHSX88Ui66zBRb/4YGob/xUWxDceirgoC1lT676asfCQ==",
"dev": true,
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/combined-stream": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -1978,6 +1978,15 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/dateformat": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
"integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -4082,6 +4091,58 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true "dev": true
}, },
"node_modules/ncc": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/ncc/-/ncc-0.3.6.tgz",
"integrity": "sha512-OXudTB2Ebt/FnOuDoPQbaa17+tdVqSOWA+gLfPxccWwsNED1uA2zEhpoB1hwdFC9yYbio/mdV5cvOtQI3Zrx1w==",
"dev": true,
"dependencies": {
"mkdirp": "^0.5.1",
"rimraf": "^2.6.1",
"tracer": "^0.8.7",
"ws": "^2.3.1"
}
},
"node_modules/ncc/node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/ncc/node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/ncc/node_modules/safe-buffer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
"integrity": "sha512-cr7dZWLwOeaFBLTIuZeYdkfO7UzGIKhjYENJFAxUOMKWGaWDm2nJM2rzxNRm5Owu0DH3ApwNo6kx5idXZfb/Iw==",
"dev": true
},
"node_modules/ncc/node_modules/ws": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz",
"integrity": "sha512-61a+9LgtYZxTq1hAonhX8Xwpo2riK4IOR/BIVxioFbCfc3QFKmpE4x9dLExfLHKtUfVZigYa36tThVhO57erEw==",
"dev": true,
"dependencies": {
"safe-buffer": "~5.0.1",
"ultron": "~1.1.0"
}
},
"node_modules/nice-try": { "node_modules/nice-try": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -5822,6 +5883,15 @@
"integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==",
"dev": true "dev": true
}, },
"node_modules/tinytim": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/tinytim/-/tinytim-0.1.1.tgz",
"integrity": "sha512-NIpsp9lBIxPNzB++HnMmUd4byzJSVbbO4F+As1Gb1IG/YQT5QvmBDjpx8SpDS8fhGC+t+Qw8ldQgbcAIaU+2cA==",
"dev": true,
"engines": {
"node": ">= 0.2.0"
}
},
"node_modules/tmpl": { "node_modules/tmpl": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -5914,6 +5984,33 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/tracer": {
"version": "0.8.15",
"resolved": "https://registry.npmjs.org/tracer/-/tracer-0.8.15.tgz",
"integrity": "sha512-ZQzlhd6zZFIpAhACiZkxLjl65XqVwi8t8UEBVGRIHAQN6nj55ftJWiFell+WSqWCP/vEycrIbUSuiyMwul+TFw==",
"dev": true,
"dependencies": {
"colors": "1.2.3",
"dateformat": "3.0.3",
"mkdirp": "^0.5.1",
"tinytim": "0.1.1"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/tracer/node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/ts-jest": { "node_modules/ts-jest": {
"version": "26.5.6", "version": "26.5.6",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz",
@@ -6030,6 +6127,12 @@
"node": ">=4.2.0" "node": ">=4.2.0"
} }
}, },
"node_modules/ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
"dev": true
},
"node_modules/underscore": { "node_modules/underscore": {
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz",
@@ -7390,12 +7493,6 @@
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
"dev": true "dev": true
}, },
"@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
},
"abab": { "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",
@@ -7923,6 +8020,12 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "dev": true
}, },
"colors": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.2.3.tgz",
"integrity": "sha512-qTfM2pNFeMZcLvf/RbrVAzDEVttZjFhaApfx9dplNjvHSX88Ui66zBRb/4YGob/xUWxDceirgoC1lT676asfCQ==",
"dev": true
},
"combined-stream": { "combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -8004,6 +8107,12 @@
"whatwg-url": "^8.0.0" "whatwg-url": "^8.0.0"
} }
}, },
"dateformat": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
"integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==",
"dev": true
},
"debug": { "debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -9619,6 +9728,54 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true "dev": true
}, },
"ncc": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/ncc/-/ncc-0.3.6.tgz",
"integrity": "sha512-OXudTB2Ebt/FnOuDoPQbaa17+tdVqSOWA+gLfPxccWwsNED1uA2zEhpoB1hwdFC9yYbio/mdV5cvOtQI3Zrx1w==",
"dev": true,
"requires": {
"mkdirp": "^0.5.1",
"rimraf": "^2.6.1",
"tracer": "^0.8.7",
"ws": "^2.3.1"
},
"dependencies": {
"mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"requires": {
"minimist": "^1.2.6"
}
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"safe-buffer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
"integrity": "sha512-cr7dZWLwOeaFBLTIuZeYdkfO7UzGIKhjYENJFAxUOMKWGaWDm2nJM2rzxNRm5Owu0DH3ApwNo6kx5idXZfb/Iw==",
"dev": true
},
"ws": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz",
"integrity": "sha512-61a+9LgtYZxTq1hAonhX8Xwpo2riK4IOR/BIVxioFbCfc3QFKmpE4x9dLExfLHKtUfVZigYa36tThVhO57erEw==",
"dev": true,
"requires": {
"safe-buffer": "~5.0.1",
"ultron": "~1.1.0"
}
}
}
},
"nice-try": { "nice-try": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -10982,6 +11139,12 @@
"integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==",
"dev": true "dev": true
}, },
"tinytim": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/tinytim/-/tinytim-0.1.1.tgz",
"integrity": "sha512-NIpsp9lBIxPNzB++HnMmUd4byzJSVbbO4F+As1Gb1IG/YQT5QvmBDjpx8SpDS8fhGC+t+Qw8ldQgbcAIaU+2cA==",
"dev": true
},
"tmpl": { "tmpl": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -11055,6 +11218,29 @@
"punycode": "^2.1.1" "punycode": "^2.1.1"
} }
}, },
"tracer": {
"version": "0.8.15",
"resolved": "https://registry.npmjs.org/tracer/-/tracer-0.8.15.tgz",
"integrity": "sha512-ZQzlhd6zZFIpAhACiZkxLjl65XqVwi8t8UEBVGRIHAQN6nj55ftJWiFell+WSqWCP/vEycrIbUSuiyMwul+TFw==",
"dev": true,
"requires": {
"colors": "1.2.3",
"dateformat": "3.0.3",
"mkdirp": "^0.5.1",
"tinytim": "0.1.1"
},
"dependencies": {
"mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"requires": {
"minimist": "^1.2.6"
}
}
}
},
"ts-jest": { "ts-jest": {
"version": "26.5.6", "version": "26.5.6",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz",
@@ -11135,6 +11321,12 @@
"integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==",
"dev": true "dev": true
}, },
"ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
"dev": true
},
"underscore": { "underscore": {
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz",
+1 -1
View File
@@ -24,8 +24,8 @@
"@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",
"ncc": "^0.3.6",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"ts-jest": "^26.0.0", "ts-jest": "^26.0.0",
"typescript": "3.9.5" "typescript": "3.9.5"
+1 -3
View File
@@ -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()
@@ -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
} }
@@ -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 =
@@ -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 =
@@ -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)}`
@@ -184,8 +183,7 @@ export async function cleanupSMI(
serviceObject.metadata.name, serviceObject.metadata.name,
GREEN_SUFFIX GREEN_SUFFIX
), ),
kind: serviceObject.kind, kind: serviceObject.kind
namespace: serviceObject?.metadata?.namespace
}) })
}) })
+4 -9
View File
@@ -195,13 +195,9 @@ async function cleanUpCanary(
files: string[], files: string[],
includeServices: boolean includeServices: boolean
): Promise<string[]> { ): 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
@@ -217,7 +213,6 @@ 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) ||
@@ -227,8 +222,8 @@ async function cleanUpCanary(
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)
} }
} }
} }
+4 -20
View File
@@ -150,15 +150,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 +164,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(
@@ -218,12 +208,7 @@ 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) { for (const file of files) {
@@ -248,7 +233,6 @@ async function annotateResources(
kubectl, kubectl,
resource.type, resource.type,
resource.name, resource.name,
resource.namespace,
annotationKeyValStr, annotationKeyValStr,
allPods allPods
) )
@@ -274,7 +258,7 @@ async function labelResources(
const labelResults = [] const labelResults = []
for (const file of files) { for (const file of files) {
try { try {
const labelResult = await kubectl.labelFiles(file, labels) const labelResult = await kubectl.labelFiles(files, labels)
labelResults.push(labelResult) labelResults.push(labelResult)
} catch (e) { } catch (e) {
core.warning(`failed to annotate resource: ${e}`) core.warning(`failed to annotate resource: ${e}`)
-2
View File
@@ -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 {
+1 -176
View File
@@ -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)
@@ -122,26 +122,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 +140,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 +165,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 +185,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 +205,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 +225,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 +245,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 +273,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 +291,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 +321,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 +331,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}
)
}) })
}) })
@@ -538,11 +369,5 @@ describe('Kubectl class', () => {
[command, '--insecure-skip-tls-verify', '--namespace', testNamespace], [command, '--insecure-skip-tls-verify', '--namespace', testNamespace],
{silent: false} {silent: false}
) )
const kubectlNoFlags = new Kubectl(kubectlPath)
kubectlNoFlags.executeCommand(command)
expect(exec.getExecOutput).toBeCalledWith(kubectlPath, [command], {
silent: false
})
}) })
}) })
+34 -60
View File
@@ -3,11 +3,11 @@ import {createInlineArray} from '../utilities/arrayUtils'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as toolCache from '@actions/tool-cache' import * as toolCache from '@actions/tool-cache'
import * as io from '@actions/io' import * as io from '@actions/io'
import {exec} from 'child_process'
export interface Resource { export interface Resource {
name: string name: string
type: string type: string
namespace?: string
} }
export class Kubectl { export class Kubectl {
@@ -20,7 +20,7 @@ export class Kubectl {
constructor( constructor(
kubectlPath: string, kubectlPath: string,
namespace: string = '', namespace: string = 'default',
ignoreSSLErrors: boolean = false, ignoreSSLErrors: boolean = false,
resourceGroup: string = '', resourceGroup: string = '',
name: string = '' name: string = ''
@@ -47,7 +47,7 @@ export class Kubectl {
] ]
if (force) applyArgs.push('--force') if (force) applyArgs.push('--force')
return await this.execute(applyArgs.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 +56,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 +86,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,14 +94,13 @@ export class Kubectl {
resourceName, resourceName,
annotation, annotation,
'--overwrite' '--overwrite'
].concat(this.getFlags(namespace)) ]
return await this.execute(args) return await this.execute(args)
} }
public async annotateFiles( public async annotateFiles(
files: string | string[], files: string | string[],
annotation: string, annotation: string
namespace?: string
): Promise<ExecOutput> { ): Promise<ExecOutput> {
const filesToAnnotate = createInlineArray(files) const filesToAnnotate = createInlineArray(files)
core.debug(`annotating ${filesToAnnotate} with annotation ${annotation}`) core.debug(`annotating ${filesToAnnotate} with annotation ${annotation}`)
@@ -128,14 +110,16 @@ export class Kubectl {
filesToAnnotate, filesToAnnotate,
annotation, annotation,
'--overwrite' '--overwrite'
].concat(this.getFlags(namespace)) ]
core.debug(
`sending args from annotate to execute: ${JSON.stringify(args)}`
)
return await this.execute(args) return await this.execute(args)
} }
public async labelFiles( public async labelFiles(
files: string | string[], files: string | string[],
labels: string[], labels: string[]
namespace?: string
): Promise<ExecOutput> { ): Promise<ExecOutput> {
const args = [ const args = [
'label', 'label',
@@ -143,59 +127,51 @@ 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, silentFailure: boolean = false
namespace?: string
): Promise<ExecOutput> { ): Promise<ExecOutput> {
core.debug( core.debug(
'fetching resource of type ' + resourceType + ' and name ' + name 'fetching resource of type ' + resourceType + ' and name ' + name
) )
return await this.execute( return await this.execute(
['get', `${resourceType}/${name}`, '-o', 'json'].concat( ['get', `${resourceType}/${name}`, '-o', 'json'],
this.getFlags(namespace)
),
silentFailure silentFailure
) )
} }
public executeCommand(command: string, args?: string) { public executeCommand(command: string, args?: string) {
if (!command) throw new Error('Command must be defined') if (!command) throw new Error('Command must be defined')
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) {
args = args.concat(this.getExecuteFlags())
core.debug(`Kubectl run with command: ${this.kubectlPath} ${args}`) core.debug(`Kubectl run with command: ${this.kubectlPath} ${args}`)
return await getExecOutput(this.kubectlPath, args, { return await getExecOutput(this.kubectlPath, args, {
@@ -203,15 +179,13 @@ export class Kubectl {
}) })
} }
protected getFlags(namespaceOverride?: string): string[] { protected getExecuteFlags(): string[] {
const flags = [] const flags = []
if (this.ignoreSSLErrors) { if (this.ignoreSSLErrors) {
flags.push('--insecure-skip-tls-verify') flags.push('--insecure-skip-tls-verify')
} }
if (this.namespace) {
const ns = namespaceOverride || this.namespace flags.push('--namespace', this.namespace)
if (ns) {
flags.push('--namespace', ns)
} }
return flags return flags
+1 -21
View File
@@ -1,32 +1,12 @@
import {PrivateKubectl} from './privatekubectl' import {PrivateKubectl} from './privatekubectl'
import * as exec from '@actions/exec'
describe('Private kubectl', () => { describe('Private kubectl', () => {
const testString = `kubectl annotate -f test.yml,test2.yml,test3.yml -f test4.yml --filename test5.yml actions.github.com/k8s-deploy={"run":"3498366832","repository":"jaiveerk/k8s-deploy","workflow":"Minikube Integration Tests - private cluster","workflowFileName":"run-integration-tests-private.yml","jobName":"run-integration-test","createdBy":"jaiveerk","runUri":"https://github.com/jaiveerk/k8s-deploy/actions/runs/3498366832","commit":"c63b323186ea1320a31290de6dcc094c06385e75","lastSuccessRunCommit":"NA","branch":"refs/heads/main","deployTimestamp":1668787848577,"dockerfilePaths":{"nginx:1.14.2":""},"manifestsPaths":["https://github.com/jaiveerk/k8s-deploy/blob/c63b323186ea1320a31290de6dcc094c06385e75/test/integration/manifests/test.yml"],"helmChartPaths":[],"provider":"GitHub"} --overwrite --namespace test-3498366832` const testString = `kubectl annotate -f 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', () => { it('should extract filenames correctly', () => {
expect(mockKube.extractFilesnames(testString)).toEqual( expect(mockKube.extractFilesnames(testString)).toEqual(
'test.yml test2.yml test3.yml test4.yml test5.yml' '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}`
)
)
})
}) })
+3 -8
View File
@@ -8,6 +8,8 @@ import * as path from 'path'
export class PrivateKubectl extends Kubectl { export class PrivateKubectl extends Kubectl {
protected async execute(args: string[], silent: boolean = false) { protected async execute(args: string[], silent: boolean = false) {
args = args.concat(this.getExecuteFlags())
args.unshift('kubectl') args.unshift('kubectl')
let kubectlCmd = args.join(' ') let kubectlCmd = args.join(' ')
let addFileFlag = false let addFileFlag = false
@@ -73,18 +75,11 @@ export class PrivateKubectl extends Kubectl {
runOutput runOutput
)}` )}`
) )
if (runOutput.exitCode !== 0) {
throw Error(
`Call to private cluster failed. Command: '${kubectlCmd}', errormessage: ${runOutput.stderr}`
)
}
const runObj: {logs: string; exitCode: number} = JSON.parse( const runObj: {logs: string; exitCode: number} = JSON.parse(
runOutput.stdout runOutput.stdout
) )
if (!silent) core.info(runObj.logs) if (!silent) core.info(runObj.logs)
if (runObj.exitCode !== 0) { if (runOutput.exitCode !== 0 && runObj.exitCode !== 0) {
throw Error(`failed private cluster Kubectl command: ${kubectlCmd}`) throw Error(`failed private cluster Kubectl command: ${kubectlCmd}`)
} }
+3 -12
View File
@@ -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
+28 -51
View File
@@ -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) {
+1 -2
View File
@@ -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 -14
View File
@@ -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)
})
}) })
}) })
+4 -12
View File
@@ -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] || ''
} }