Compare commits

..

2 Commits

Author SHA1 Message Date
Oliver King c86a95c404 Add node modules and compiled JavaScript from main 2022-11-23 21:06:24 +00:00
Oliver King 2cfbf514e9 Merge branch 'releases/v4' into tmp 2022-11-23 21:06:04 +00:00
23 changed files with 455 additions and 624 deletions
+9 -4
View File
@@ -13,15 +13,20 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v2
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@v2
uses: github/codeql-action/init@v1
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@@ -29,7 +34,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@v2
uses: github/codeql-action/autobuild@v1
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -43,4 +48,4 @@ jobs:
# make release
- 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:
build: # make sure build/ci works properly
name: Run Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v1
- run: |
npm install
npm test
+9 -9
View File
@@ -128,7 +128,7 @@ Following are the key capabilities of this action:
### Basic deployment (without any deployment strategy)
```yaml
- uses: Azure/k8s-deploy@v4
- uses: Azure/k8s-deploy@v3.1
with:
namespace: 'myapp'
manifests: |
@@ -162,7 +162,7 @@ Following are the key capabilities of this action:
### Canary deployment without service mesh
```yaml
- uses: Azure/k8s-deploy@v4
- uses: Azure/k8s-deploy@v3.1
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
@@ -181,7 +181,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@v4
- uses: Azure/k8s-deploy@v3.1
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
@@ -199,7 +199,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@v4
- uses: Azure/k8s-deploy@v3.1
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
@@ -220,7 +220,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@v4
- uses: Azure/k8s-deploy@v3.1
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }} '
@@ -239,7 +239,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@v4
- uses: Azure/k8s-deploy@v3.1
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
@@ -259,7 +259,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@v4
- uses: Azure/k8s-deploy@v3.1
with:
namespace: 'myapp'
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
@@ -316,7 +316,7 @@ jobs:
container-registry-password: ${{ secrets.REGISTRY_PASSWORD }}
secret-name: demo-k8s-secret
- uses: Azure/k8s-deploy@v4
- uses: Azure/k8s-deploy@v3.1
with:
action: deploy
manifests: |
@@ -362,7 +362,7 @@ jobs:
container-registry-password: ${{ secrets.REGISTRY_PASSWORD }}
secret-name: demo-k8s-secret
- uses: Azure/k8s-deploy@v4
- uses: Azure/k8s-deploy@v3.1
with:
action: deploy
manifests: |
+115 -165
View File
@@ -140,6 +140,7 @@ const file_command_1 = __nccwpck_require__(8466);
const utils_1 = __nccwpck_require__(7369);
const os = __importStar(__nccwpck_require__(2037));
const path = __importStar(__nccwpck_require__(1017));
const uuid_1 = __nccwpck_require__(487);
const oidc_utils_1 = __nccwpck_require__(7557);
/**
* The code to exit an action
@@ -169,9 +170,20 @@ function exportVariable(name, val) {
process.env[name] = convertedVal;
const filePath = process.env['GITHUB_ENV'] || '';
if (filePath) {
return file_command_1.issueFileCommand('ENV', file_command_1.prepareKeyValueMessage(name, val));
const delimiter = `ghadelimiter_${uuid_1.v4()}`;
// These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter.
if (name.includes(delimiter)) {
throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`);
}
if (convertedVal.includes(delimiter)) {
throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
}
const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`;
file_command_1.issueCommand('ENV', commandValue);
}
else {
command_1.issueCommand('set-env', { name }, convertedVal);
}
}
exports.exportVariable = exportVariable;
/**
@@ -189,7 +201,7 @@ exports.setSecret = setSecret;
function addPath(inputPath) {
const filePath = process.env['GITHUB_PATH'] || '';
if (filePath) {
file_command_1.issueFileCommand('PATH', inputPath);
file_command_1.issueCommand('PATH', inputPath);
}
else {
command_1.issueCommand('add-path', {}, inputPath);
@@ -229,10 +241,7 @@ function getMultilineInput(name, options) {
const inputs = getInput(name, options)
.split('\n')
.filter(x => x !== '');
if (options && options.trimWhitespace === false) {
return inputs;
}
return inputs.map(input => input.trim());
}
exports.getMultilineInput = getMultilineInput;
/**
@@ -265,12 +274,8 @@ exports.getBooleanInput = getBooleanInput;
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setOutput(name, value) {
const filePath = process.env['GITHUB_OUTPUT'] || '';
if (filePath) {
return file_command_1.issueFileCommand('OUTPUT', file_command_1.prepareKeyValueMessage(name, value));
}
process.stdout.write(os.EOL);
command_1.issueCommand('set-output', { name }, utils_1.toCommandValue(value));
command_1.issueCommand('set-output', { name }, value);
}
exports.setOutput = setOutput;
/**
@@ -399,11 +404,7 @@ exports.group = group;
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function saveState(name, value) {
const filePath = process.env['GITHUB_STATE'] || '';
if (filePath) {
return file_command_1.issueFileCommand('STATE', file_command_1.prepareKeyValueMessage(name, value));
}
command_1.issueCommand('save-state', { name }, utils_1.toCommandValue(value));
command_1.issueCommand('save-state', { name }, value);
}
exports.saveState = saveState;
/**
@@ -469,14 +470,13 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.prepareKeyValueMessage = exports.issueFileCommand = void 0;
exports.issueCommand = void 0;
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
const fs = __importStar(__nccwpck_require__(7147));
const os = __importStar(__nccwpck_require__(2037));
const uuid_1 = __nccwpck_require__(487);
const utils_1 = __nccwpck_require__(7369);
function issueFileCommand(command, message) {
function issueCommand(command, message) {
const filePath = process.env[`GITHUB_${command}`];
if (!filePath) {
throw new Error(`Unable to find environment variable for file command ${command}`);
@@ -488,22 +488,7 @@ function issueFileCommand(command, message) {
encoding: 'utf8'
});
}
exports.issueFileCommand = issueFileCommand;
function prepareKeyValueMessage(key, value) {
const delimiter = `ghadelimiter_${uuid_1.v4()}`;
const convertedValue = utils_1.toCommandValue(value);
// These should realistically never happen, but just in case someone finds a
// way to exploit uuid generation let's not allow keys or values that contain
// the delimiter.
if (key.includes(delimiter)) {
throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`);
}
if (convertedValue.includes(delimiter)) {
throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
}
return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`;
}
exports.prepareKeyValueMessage = prepareKeyValueMessage;
exports.issueCommand = issueCommand;
//# sourceMappingURL=file-command.js.map
/***/ }),
@@ -20210,7 +20195,7 @@ function deploy(kubectl, manifestFilePaths, deploymentStrategy) {
KubernetesConstants.DiscoveryAndLoadBalancerResource.INGRESS
]);
for (const ingressResource of ingressResources) {
yield kubectl.getResource(KubernetesConstants.DiscoveryAndLoadBalancerResource.INGRESS, ingressResource.name, false, ingressResource.namespace);
yield kubectl.getResource(KubernetesConstants.DiscoveryAndLoadBalancerResource.INGRESS, ingressResource.name);
}
core.endGroup();
// annotate resources
@@ -20593,8 +20578,7 @@ function deleteGreenObjects(kubectl, toDelete) {
const resourcesToDelete = toDelete.map((obj) => {
return {
name: getBlueGreenResourceName(obj.metadata.name, exports.GREEN_SUFFIX),
kind: obj.kind,
namespace: obj.metadata.namespace
kind: obj.kind
};
});
core.debug(`deleting green objects: ${JSON.stringify(resourcesToDelete)}`);
@@ -20621,24 +20605,28 @@ exports.deleteObjects = deleteObjects;
// other common functions
function getManifestObjects(filePaths) {
const deploymentEntityList = [];
const serviceEntityList = [];
const routedServiceEntityList = [];
const unroutedServiceEntityList = [];
const ingressEntityList = [];
const otherEntitiesList = [];
const serviceNameMap = new Map();
// Manifest objects per type. All resources should be parsed and
// organized before we can check if services are “routed” or not.
filePaths.forEach((filePath) => {
const fileContents = fs.readFileSync(filePath).toString();
yaml.safeLoadAll(fileContents, (inputObject) => {
if (!!inputObject) {
const kind = inputObject.kind;
const name = inputObject.metadata.name;
if (kubernetesTypes_1.isDeploymentEntity(kind)) {
deploymentEntityList.push(inputObject);
}
else if (kubernetesTypes_1.isServiceEntity(kind)) {
serviceEntityList.push(inputObject);
if (isServiceRouted(inputObject, deploymentEntityList)) {
routedServiceEntityList.push(inputObject);
serviceNameMap.set(name, getBlueGreenResourceName(name, exports.GREEN_SUFFIX));
}
else {
unroutedServiceEntityList.push(inputObject);
}
}
else if (kubernetesTypes_1.isIngressEntity(kind)) {
ingressEntityList.push(inputObject);
@@ -20649,16 +20637,6 @@ function getManifestObjects(filePaths) {
}
});
});
serviceEntityList.forEach((inputObject) => {
if (isServiceRouted(inputObject, deploymentEntityList)) {
const name = inputObject.metadata.name;
routedServiceEntityList.push(inputObject);
serviceNameMap.set(name, getBlueGreenResourceName(name, exports.GREEN_SUFFIX));
}
else {
unroutedServiceEntityList.push(inputObject);
}
});
return {
serviceEntityList: routedServiceEntityList,
serviceNameMap: serviceNameMap,
@@ -20753,9 +20731,9 @@ function isServiceSelectorSubsetOfMatchLabel(serviceSelector, matchLabels) {
return isMatch;
}
exports.isServiceSelectorSubsetOfMatchLabel = isServiceSelectorSubsetOfMatchLabel;
function fetchResource(kubectl, kind, name, namespace) {
function fetchResource(kubectl, kind, name) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield kubectl.getResource(kind, name, false, namespace);
const result = yield kubectl.getResource(kind, name);
if (result == null || !!result.stderr) {
return null;
}
@@ -20955,16 +20933,16 @@ function isIngressRouted(ingressObject, serviceNameMap) {
}
exports.isIngressRouted = isIngressRouted;
function validateIngresses(kubectl, ingressEntityList, serviceNameMap) {
var _a, _b;
var _a;
return __awaiter(this, void 0, void 0, function* () {
let areValid = true;
const invalidIngresses = [];
for (const inputObject of ingressEntityList) {
if (isIngressRouted(inputObject, serviceNameMap)) {
//querying existing ingress
const existingIngress = yield blueGreenHelper_1.fetchResource(kubectl, inputObject.kind, inputObject.metadata.name, (_a = inputObject === null || inputObject === void 0 ? void 0 : inputObject.metadata) === null || _a === void 0 ? void 0 : _a.namespace);
const existingIngress = yield blueGreenHelper_1.fetchResource(kubectl, inputObject.kind, inputObject.metadata.name);
const isValid = !!existingIngress &&
((_b = existingIngress === null || existingIngress === void 0 ? void 0 : existingIngress.metadata) === null || _b === void 0 ? void 0 : _b.labels[blueGreenHelper_1.BLUE_GREEN_VERSION_LABEL]) ===
((_a = existingIngress === null || existingIngress === void 0 ? void 0 : existingIngress.metadata) === null || _a === void 0 ? void 0 : _a.labels[blueGreenHelper_1.BLUE_GREEN_VERSION_LABEL]) ===
blueGreenHelper_1.GREEN_LABEL_VALUE;
if (!isValid) {
core.debug(`Invalid ingress detected (must be in green state): ${JSON.stringify(inputObject)}`);
@@ -21222,12 +21200,11 @@ function getUpdatedBlueGreenService(inputObject, labelValue) {
}
exports.getUpdatedBlueGreenService = getUpdatedBlueGreenService;
function validateServicesState(kubectl, serviceEntityList) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
let areServicesGreen = true;
for (const serviceObject of serviceEntityList) {
// finding the existing routed service
const existingService = yield blueGreenHelper_1.fetchResource(kubectl, serviceObject.kind, serviceObject.metadata.name, (_a = serviceObject === null || serviceObject === void 0 ? void 0 : serviceObject.metadata) === null || _a === void 0 ? void 0 : _a.namespace);
const existingService = yield blueGreenHelper_1.fetchResource(kubectl, serviceObject.kind, serviceObject.metadata.name);
let isServiceGreen = !!existingService &&
getServiceSpecLabel(existingService) ==
blueGreenHelper_1.GREEN_LABEL_VALUE;
@@ -21348,12 +21325,11 @@ function getGreenSMIServiceResource(inputObject) {
}
exports.getGreenSMIServiceResource = getGreenSMIServiceResource;
function validateTrafficSplitsState(kubectl, serviceEntityList) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
let trafficSplitsInRightState = true;
for (const serviceObject of serviceEntityList) {
const name = serviceObject.metadata.name;
let trafficSplitObject = yield blueGreenHelper_1.fetchResource(kubectl, exports.TRAFFIC_SPLIT_OBJECT, blueGreenHelper_1.getBlueGreenResourceName(name, exports.TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX), (_a = serviceObject === null || serviceObject === void 0 ? void 0 : serviceObject.metadata) === null || _a === void 0 ? void 0 : _a.namespace);
let trafficSplitObject = yield blueGreenHelper_1.fetchResource(kubectl, exports.TRAFFIC_SPLIT_OBJECT, blueGreenHelper_1.getBlueGreenResourceName(name, exports.TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX));
core.debug(`ts object extracted was ${JSON.stringify(trafficSplitObject)}`);
if (!trafficSplitObject) {
core.debug(`no traffic split exits for ${name}`);
@@ -21380,11 +21356,9 @@ function cleanupSMI(kubectl, serviceEntityList) {
return __awaiter(this, void 0, void 0, function* () {
const deleteList = [];
serviceEntityList.forEach((serviceObject) => {
var _a;
deleteList.push({
name: blueGreenHelper_1.getBlueGreenResourceName(serviceObject.metadata.name, blueGreenHelper_1.GREEN_SUFFIX),
kind: serviceObject.kind,
namespace: (_a = serviceObject === null || serviceObject === void 0 ? void 0 : serviceObject.metadata) === null || _a === void 0 ? void 0 : _a.namespace
kind: serviceObject.kind
});
});
// delete all objects
@@ -21548,12 +21522,11 @@ function addCanaryLabelsAndAnnotations(inputObject, type) {
}
}
function cleanUpCanary(kubectl, files, includeServices) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const deleteObject = function (kind, name, namespace) {
const deleteObject = function (kind, name) {
return __awaiter(this, void 0, void 0, function* () {
try {
const result = yield kubectl.delete([kind, name], namespace);
const result = yield kubectl.delete([kind, name]);
kubectlUtils_1.checkForErrors([result]);
}
catch (ex) {
@@ -21568,14 +21541,13 @@ function cleanUpCanary(kubectl, files, includeServices) {
for (const inputObject of parsedYaml) {
const name = inputObject.metadata.name;
const kind = inputObject.kind;
const namespace = (_a = inputObject === null || inputObject === void 0 ? void 0 : inputObject.metadata) === null || _a === void 0 ? void 0 : _a.namespace;
if (kubernetesTypes_1.isDeploymentEntity(kind) ||
(includeServices && kubernetesTypes_1.isServiceEntity(kind))) {
deletedFiles.push(filePath);
const canaryObjectName = getCanaryResourceName(name);
const baselineObjectName = getBaselineResourceName(name);
yield deleteObject(kind, canaryObjectName, namespace);
yield deleteObject(kind, baselineObjectName, namespace);
yield deleteObject(kind, canaryObjectName);
yield deleteObject(kind, baselineObjectName);
}
}
}
@@ -22030,20 +22002,12 @@ function checkManifestStability(kubectl, resources) {
exports.checkManifestStability = checkManifestStability;
function annotateAndLabelResources(files, kubectl, resourceTypes, allPods) {
return __awaiter(this, void 0, void 0, function* () {
const defaultWorkflowFileName = 'k8s-deploy-failed-workflow-annotation';
const githubToken = core.getInput('token');
let workflowFilePath;
try {
workflowFilePath = yield githubUtils_1.getWorkflowFilePath(githubToken);
}
catch (ex) {
core.warning(`Failed to extract workflow file name: ${ex}`);
workflowFilePath = defaultWorkflowFileName;
}
const workflowFilePath = yield githubUtils_1.getWorkflowFilePath(githubToken);
const deploymentConfig = yield dockerUtils_1.getDeploymentConfig();
const annotationKeyLabel = workflowAnnotationUtils_1.getWorkflowAnnotationKeyLabel();
yield annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel, workflowFilePath, deploymentConfig).catch((err) => core.warning(`Failed to annotate resources: ${err} `));
yield labelResources(files, kubectl, annotationKeyLabel).catch((err) => core.warning(`Failed to label resources: ${err}`));
yield annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel, workflowFilePath, deploymentConfig);
yield labelResources(files, kubectl, annotationKeyLabel);
});
}
exports.annotateAndLabelResources = annotateAndLabelResources;
@@ -22066,7 +22030,7 @@ function annotateResources(files, kubectl, resourceTypes, allPods, annotationKey
const annotationKeyValStr = `${annotationKey}=${workflowAnnotationUtils_1.getWorkflowAnnotations(lastSuccessSha, workflowFilePath, deploymentConfig)}`;
const annotateNamespace = !(core.getInput('annotate-namespace').toLowerCase() === 'false');
if (annotateNamespace) {
annotateResults.push(yield kubectl.annotate('namespace', namespace, annotationKeyValStr, namespace));
annotateResults.push(yield kubectl.annotate('namespace', namespace, annotationKeyValStr));
}
for (const file of files) {
try {
@@ -22081,7 +22045,7 @@ function annotateResources(files, kubectl, resourceTypes, allPods, annotationKey
if (resource.type.toLowerCase() !==
models.KubernetesWorkload.POD.toLowerCase()) {
;
(yield kubectlUtils_1.annotateChildPods(kubectl, resource.type, resource.name, resource.namespace, annotationKeyValStr, allPods)).forEach((execResult) => annotateResults.push(execResult));
(yield kubectlUtils_1.annotateChildPods(kubectl, resource.type, resource.name, annotationKeyValStr, allPods)).forEach((execResult) => annotateResults.push(execResult));
}
}
kubectlUtils_1.checkForErrors(annotateResults, true);
@@ -22096,7 +22060,7 @@ function labelResources(files, kubectl, label) {
const labelResults = [];
for (const file of files) {
try {
const labelResult = yield kubectl.labelFiles(file, labels);
const labelResult = yield kubectl.labelFiles(files, labels);
labelResults.push(labelResult);
}
catch (e) {
@@ -22344,7 +22308,7 @@ const core = __nccwpck_require__(6024);
const toolCache = __nccwpck_require__(3594);
const io = __nccwpck_require__(6202);
class Kubectl {
constructor(kubectlPath, namespace = '', ignoreSSLErrors = false, resourceGroup = '', name = '') {
constructor(kubectlPath, namespace = 'default', ignoreSSLErrors = false, resourceGroup = '', name = '') {
this.kubectlPath = kubectlPath;
this.ignoreSSLErrors = !!ignoreSSLErrors;
this.namespace = namespace;
@@ -22363,41 +22327,37 @@ class Kubectl {
];
if (force)
applyArgs.push('--force');
return yield this.execute(applyArgs.concat(this.getFlags()));
return yield this.execute(applyArgs);
}
catch (err) {
core.debug('Kubectl apply failed:' + err);
}
});
}
describe(resourceType, resourceName, silent = false, namespace) {
describe(resourceType, resourceName, silent = false) {
return __awaiter(this, void 0, void 0, function* () {
return yield this.execute(['describe', resourceType, resourceName].concat(this.getFlags(namespace)), silent);
return yield this.execute(['describe', resourceType, resourceName], silent);
});
}
getNewReplicaSet(deployment, namespace) {
getNewReplicaSet(deployment) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.describe('deployment', deployment, true, namespace);
const result = yield this.describe('deployment', deployment, true);
let newReplicaSet = '';
if (result === null || result === void 0 ? void 0 : result.stdout) {
const stdout = result.stdout.split('\n');
core.debug('stdout from getNewReplicaSet is ' + JSON.stringify(stdout));
stdout.forEach((line) => {
const newreplicaset = 'newreplicaset';
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()}`);
if (line && line.toLowerCase().indexOf(newreplicaset) > -1)
newReplicaSet = line
.substring(newreplicaset.length)
.trim()
.split(' ')[0];
}
});
}
return newReplicaSet;
});
}
annotate(resourceType, resourceName, annotation, namespace) {
annotate(resourceType, resourceName, annotation) {
return __awaiter(this, void 0, void 0, function* () {
const args = [
'annotate',
@@ -22405,11 +22365,11 @@ class Kubectl {
resourceName,
annotation,
'--overwrite'
].concat(this.getFlags(namespace));
];
return yield this.execute(args);
});
}
annotateFiles(files, annotation, namespace) {
annotateFiles(files, annotation) {
return __awaiter(this, void 0, void 0, function* () {
const filesToAnnotate = arrayUtils_1.createInlineArray(files);
core.debug(`annotating ${filesToAnnotate} with annotation ${annotation}`);
@@ -22419,11 +22379,12 @@ class Kubectl {
filesToAnnotate,
annotation,
'--overwrite'
].concat(this.getFlags(namespace));
];
core.debug(`sending args from annotate to execute: ${JSON.stringify(args)}`);
return yield this.execute(args);
});
}
labelFiles(files, labels, namespace) {
labelFiles(files, labels) {
return __awaiter(this, void 0, void 0, function* () {
const args = [
'label',
@@ -22431,53 +22392,56 @@ class Kubectl {
arrayUtils_1.createInlineArray(files),
...labels,
'--overwrite'
].concat(this.getFlags(namespace));
];
return yield this.execute(args);
});
}
getAllPods() {
return __awaiter(this, void 0, void 0, function* () {
return yield this.execute(['get', 'pods', '-o', 'json'].concat(this.getFlags()), true);
return yield this.execute(['get', 'pods', '-o', 'json'], true);
});
}
checkRolloutStatus(resourceType, name, namespace) {
checkRolloutStatus(resourceType, name) {
return __awaiter(this, void 0, void 0, function* () {
return yield this.execute(['rollout', 'status', `${resourceType}/${name}`].concat(this.getFlags(namespace)));
return yield this.execute([
'rollout',
'status',
`${resourceType}/${name}`
]);
});
}
getResource(resourceType, name, silentFailure = false, namespace) {
getResource(resourceType, name, silentFailure = false) {
return __awaiter(this, void 0, void 0, function* () {
core.debug('fetching resource of type ' + resourceType + ' and name ' + name);
return yield this.execute(['get', `${resourceType}/${name}`, '-o', 'json'].concat(this.getFlags(namespace)), silentFailure);
return yield this.execute(['get', `${resourceType}/${name}`, '-o', 'json'], silentFailure);
});
}
executeCommand(command, args) {
if (!command)
throw new Error('Command must be defined');
const a = args ? [args] : [];
return this.execute([command, ...a.concat(this.getFlags())]);
return args ? this.execute([command, args]) : this.execute([command]);
}
delete(args, namespace) {
delete(args) {
if (typeof args === 'string')
return this.execute(['delete', args].concat(this.getFlags(namespace)));
return this.execute(['delete', ...args.concat(this.getFlags(namespace))]);
return this.execute(['delete', args]);
return this.execute(['delete', ...args]);
}
execute(args, silent = false) {
return __awaiter(this, void 0, void 0, function* () {
args = args.concat(this.getExecuteFlags());
core.debug(`Kubectl run with command: ${this.kubectlPath} ${args}`);
return yield exec_1.getExecOutput(this.kubectlPath, args, {
silent
});
});
}
getFlags(namespaceOverride) {
getExecuteFlags() {
const flags = [];
if (this.ignoreSSLErrors) {
flags.push('--insecure-skip-tls-verify');
}
const ns = namespaceOverride || this.namespace;
if (ns) {
flags.push('--namespace', ns);
if (this.namespace) {
flags.push('--namespace', this.namespace);
}
return flags;
}
@@ -22608,6 +22572,7 @@ const path = __nccwpck_require__(1017);
class PrivateKubectl extends kubectl_1.Kubectl {
execute(args, silent = false) {
return __awaiter(this, void 0, void 0, function* () {
args = args.concat(this.getExecuteFlags());
args.unshift('kubectl');
let kubectlCmd = args.join(' ');
let addFileFlag = false;
@@ -22657,13 +22622,10 @@ class PrivateKubectl extends kubectl_1.Kubectl {
core.debug(`full form of az command: az ${allArgs.join(' ')}`);
const runOutput = yield exec_1.getExecOutput('az', allArgs, eo);
core.debug(`from kubectl private cluster command got run output ${JSON.stringify(runOutput)}`);
if (runOutput.exitCode !== 0) {
throw Error(`Call to private cluster failed. Command: '${kubectlCmd}', errormessage: ${runOutput.stderr}`);
}
const runObj = JSON.parse(runOutput.stdout);
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}`);
}
return {
@@ -23171,7 +23133,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.annotateChildPods = exports.getLastSuccessfulRunSha = exports.checkForErrors = void 0;
const core = __nccwpck_require__(6024);
const NAMESPACE = 'namespace';
function checkForErrors(execResults, warnIfError) {
let stderr = '';
execResults.forEach((result) => {
@@ -23195,7 +23156,7 @@ exports.checkForErrors = checkForErrors;
function getLastSuccessfulRunSha(kubectl, namespaceName, annotationKey) {
return __awaiter(this, void 0, void 0, function* () {
try {
const result = yield kubectl.getResource(NAMESPACE, namespaceName, false, namespaceName);
const result = yield kubectl.getResource('namespace', namespaceName);
if (result === null || result === void 0 ? void 0 : result.stderr) {
core.warning(result.stderr);
return process.env.GITHUB_SHA;
@@ -23218,12 +23179,12 @@ function getLastSuccessfulRunSha(kubectl, namespaceName, annotationKey) {
});
}
exports.getLastSuccessfulRunSha = getLastSuccessfulRunSha;
function annotateChildPods(kubectl, resourceType, resourceName, namespace, annotationKeyValStr, allPods) {
function annotateChildPods(kubectl, resourceType, resourceName, annotationKeyValStr, allPods) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
let owner = resourceName;
if (resourceType.toLowerCase().indexOf('deployment') > -1) {
owner = yield kubectl.getNewReplicaSet(resourceName, namespace);
owner = yield kubectl.getNewReplicaSet(resourceName);
}
const commandExecutionResults = [];
if ((allPods === null || allPods === void 0 ? void 0 : allPods.items) && ((_a = allPods.items) === null || _a === void 0 ? void 0 : _a.length) > 0) {
@@ -23233,7 +23194,7 @@ function annotateChildPods(kubectl, resourceType, resourceName, namespace, annot
if (owners) {
for (const ownerRef of owners) {
if (ownerRef.name === owner) {
commandExecutionResults.push(kubectl.annotate('pod', pod.metadata.name, annotationKeyValStr, namespace));
commandExecutionResults.push(kubectl.annotate('pod', pod.metadata.name, annotationKeyValStr));
break;
}
}
@@ -23388,8 +23349,6 @@ const core = __nccwpck_require__(6024);
const KubernetesConstants = __nccwpck_require__(6583);
const kubectlUtils_1 = __nccwpck_require__(5474);
const timeUtils_1 = __nccwpck_require__(4046);
const IS_SILENT = false;
const POD = 'pod';
function checkManifestStability(kubectl, resources) {
return __awaiter(this, void 0, void 0, function* () {
let rolloutStatusHasErrors = false;
@@ -23397,32 +23356,32 @@ function checkManifestStability(kubectl, resources) {
const resource = resources[i];
if (KubernetesConstants.WORKLOAD_TYPES_WITH_ROLLOUT_STATUS.indexOf(resource.type.toLowerCase()) >= 0) {
try {
const result = yield kubectl.checkRolloutStatus(resource.type, resource.name, resource.namespace);
const result = yield kubectl.checkRolloutStatus(resource.type, resource.name);
kubectlUtils_1.checkForErrors([result]);
}
catch (ex) {
core.error(ex);
yield kubectl.describe(resource.type, resource.name, IS_SILENT, resource.namespace);
yield kubectl.describe(resource.type, resource.name);
rolloutStatusHasErrors = true;
}
}
if (resource.type == KubernetesConstants.KubernetesWorkload.POD) {
try {
yield checkPodStatus(kubectl, resource);
yield checkPodStatus(kubectl, resource.name);
}
catch (ex) {
core.warning(`Could not determine pod status: ${JSON.stringify(ex)}`);
yield kubectl.describe(resource.type, resource.name, IS_SILENT, resource.namespace);
yield kubectl.describe(resource.type, resource.name);
}
}
if (resource.type ==
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE) {
try {
const service = yield getService(kubectl, resource);
const service = yield getService(kubectl, resource.name);
const { spec, status } = service;
if (spec.type === KubernetesConstants.ServiceTypes.LOAD_BALANCER) {
if (!isLoadBalancerIPAssigned(status)) {
yield waitForServiceExternalIPAssignment(kubectl, resource);
yield waitForServiceExternalIPAssignment(kubectl, resource.name);
}
else {
core.info(`ServiceExternalIP ${resource.name} ${status.loadBalancer.ingress[0].ip}`);
@@ -23431,7 +23390,7 @@ function checkManifestStability(kubectl, resources) {
}
catch (ex) {
core.warning(`Could not determine service status of: ${resource.name} Error: ${ex}`);
yield kubectl.describe(resource.type, resource.name, IS_SILENT, resource.namespace);
yield kubectl.describe(resource.type, resource.name);
}
}
}
@@ -23441,7 +23400,7 @@ function checkManifestStability(kubectl, resources) {
});
}
exports.checkManifestStability = checkManifestStability;
function checkPodStatus(kubectl, pod) {
function checkPodStatus(kubectl, podName) {
return __awaiter(this, void 0, void 0, function* () {
const sleepTimeout = 10 * 1000; // 10 seconds
const iterations = 60; // 60 * 10 seconds timeout = 10 minutes max timeout
@@ -23449,20 +23408,20 @@ function checkPodStatus(kubectl, pod) {
let kubectlDescribeNeeded = false;
for (let i = 0; i < iterations; i++) {
yield timeUtils_1.sleep(sleepTimeout);
core.debug(`Polling for pod status: ${pod.name}`);
podStatus = yield getPodStatus(kubectl, pod);
core.debug(`Polling for pod status: ${podName}`);
podStatus = yield getPodStatus(kubectl, podName);
if (podStatus &&
(podStatus === null || podStatus === void 0 ? void 0 : podStatus.phase) !== 'Pending' &&
(podStatus === null || podStatus === void 0 ? void 0 : podStatus.phase) !== 'Unknown') {
break;
}
}
podStatus = yield getPodStatus(kubectl, pod);
podStatus = yield getPodStatus(kubectl, podName);
switch (podStatus.phase) {
case 'Succeeded':
case 'Running':
if (isPodReady(podStatus)) {
console.log(`pod/${pod.name} is successfully rolled out`);
console.log(`pod/${podName} is successfully rolled out`);
}
else {
kubectlDescribeNeeded = true;
@@ -23470,26 +23429,26 @@ function checkPodStatus(kubectl, pod) {
break;
case 'Pending':
if (!isPodReady(podStatus)) {
core.warning(`pod/${pod.name} rollout status check timed out`);
core.warning(`pod/${podName} rollout status check timed out`);
kubectlDescribeNeeded = true;
}
break;
case 'Failed':
core.error(`pod/${pod.name} rollout failed`);
core.error(`pod/${podName} rollout failed`);
kubectlDescribeNeeded = true;
break;
default:
core.warning(`pod/${pod.name} rollout status: ${podStatus.phase}`);
core.warning(`pod/${podName} rollout status: ${podStatus.phase}`);
}
if (kubectlDescribeNeeded) {
yield kubectl.describe(POD, pod.name, IS_SILENT, pod.namespace);
yield kubectl.describe('pod', podName);
}
});
}
exports.checkPodStatus = checkPodStatus;
function getPodStatus(kubectl, pod) {
function getPodStatus(kubectl, podName) {
return __awaiter(this, void 0, void 0, function* () {
const podResult = yield kubectl.getResource(POD, pod.name, IS_SILENT, pod.namespace);
const podResult = yield kubectl.getResource('pod', podName);
kubectlUtils_1.checkForErrors([podResult]);
return JSON.parse(podResult.stdout).status;
});
@@ -23507,27 +23466,27 @@ function isPodReady(podStatus) {
}
return allContainersAreReady;
}
function getService(kubectl, service) {
function getService(kubectl, serviceName) {
return __awaiter(this, void 0, void 0, function* () {
const serviceResult = yield kubectl.getResource(KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE, service.name, IS_SILENT, service.namespace);
const serviceResult = yield kubectl.getResource(KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE, serviceName);
kubectlUtils_1.checkForErrors([serviceResult]);
return JSON.parse(serviceResult.stdout);
});
}
function waitForServiceExternalIPAssignment(kubectl, service) {
function waitForServiceExternalIPAssignment(kubectl, serviceName) {
return __awaiter(this, void 0, void 0, function* () {
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 : ${service.name}`);
core.info(`Wait for service ip assignment : ${serviceName}`);
yield timeUtils_1.sleep(sleepTimeout);
const status = (yield getService(kubectl, service)).status;
const status = (yield getService(kubectl, serviceName)).status;
if (isLoadBalancerIPAssigned(status)) {
core.info(`ServiceExternalIP ${service.name} ${status.loadBalancer.ingress[0].ip}`);
core.info(`ServiceExternalIP ${serviceName} ${status.loadBalancer.ingress[0].ip}`);
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) {
@@ -23736,13 +23695,11 @@ function getResources(filePaths, filterResourceTypes) {
filePaths.forEach((filePath) => {
const fileContents = fs.readFileSync(filePath).toString();
yaml.safeLoadAll(fileContents, (inputObject) => {
var _a;
const inputObjectKind = (inputObject === null || inputObject === void 0 ? void 0 : inputObject.kind) || '';
if (filterResourceTypes.filter((type) => inputObjectKind.toLowerCase() === type.toLowerCase()).length > 0) {
resources.push({
type: inputObject.kind,
name: inputObject.metadata.name,
namespace: (_a = inputObject === null || inputObject === void 0 ? void 0 : inputObject.metadata) === null || _a === void 0 ? void 0 : _a.namespace
name: inputObject.metadata.name
});
}
});
@@ -23831,9 +23788,8 @@ exports.getTrafficSplitAPIVersion = getTrafficSplitAPIVersion;
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.removeInvalidLabelCharacters = exports.cleanLabel = exports.getWorkflowAnnotationKeyLabel = exports.getWorkflowAnnotations = exports.VALID_LABEL_REGEX = void 0;
exports.cleanLabel = exports.getWorkflowAnnotationKeyLabel = exports.getWorkflowAnnotations = void 0;
const ANNOTATION_PREFIX = 'actions.github.com';
exports.VALID_LABEL_REGEX = /([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]/;
function getWorkflowAnnotations(lastSuccessRunSha, workflowFilePath, deploymentConfig) {
const annotationObject = {
run: process.env.GITHUB_RUN_ID,
@@ -23865,20 +23821,14 @@ exports.getWorkflowAnnotationKeyLabel = getWorkflowAnnotationKeyLabel;
* @returns cleaned label
*/
function cleanLabel(label) {
let removedInvalidChars = removeInvalidLabelCharacters(label);
const regexResult = exports.VALID_LABEL_REGEX.exec(removedInvalidChars) || [
'github-workflow-file'
];
return regexResult[0];
}
exports.cleanLabel = cleanLabel;
function removeInvalidLabelCharacters(label) {
return label
let removedInvalidChars = 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] || '';
}
exports.removeInvalidLabelCharacters = removeInvalidLabelCharacters;
exports.cleanLabel = cleanLabel;
/***/ }),
+215 -23
View File
@@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/core": "^1.9.1",
"@actions/exec": "^1.0.0",
"@actions/io": "^1.0.0",
"@actions/tool-cache": "1.1.2",
@@ -22,17 +22,17 @@
"@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"
}
},
"node_modules/@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
@@ -1183,15 +1183,6 @@
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
"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": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -1884,6 +1875,15 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"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": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -1978,6 +1978,15 @@
"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": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -4082,6 +4091,58 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"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": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -5822,6 +5883,15 @@
"integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==",
"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": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -5914,6 +5984,33 @@
"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": {
"version": "26.5.6",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz",
@@ -6030,6 +6127,12 @@
"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": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz",
@@ -6426,9 +6529,9 @@
},
"dependencies": {
"@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
"requires": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
@@ -7390,12 +7493,6 @@
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
"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": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -7923,6 +8020,12 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"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": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -8004,6 +8107,12 @@
"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": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -9619,6 +9728,54 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"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": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -10982,6 +11139,12 @@
"integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==",
"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": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -11055,6 +11218,29 @@
"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": {
"version": "26.5.6",
"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==",
"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": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz",
+2 -2
View File
@@ -11,7 +11,7 @@
"format-check": "prettier --check ."
},
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/core": "^1.9.1",
"@actions/exec": "^1.0.0",
"@actions/io": "^1.0.0",
"@actions/tool-cache": "1.1.2",
@@ -24,8 +24,8 @@
"@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"
+1 -3
View File
@@ -56,9 +56,7 @@ export async function deploy(
for (const ingressResource of ingressResources) {
await kubectl.getResource(
KubernetesConstants.DiscoveryAndLoadBalancerResource.INGRESS,
ingressResource.name,
false,
ingressResource.namespace
ingressResource.name
)
}
core.endGroup()
@@ -38,8 +38,7 @@ export async function deleteGreenObjects(
const resourcesToDelete: K8sDeleteObject[] = toDelete.map((obj) => {
return {
name: getBlueGreenResourceName(obj.metadata.name, GREEN_SUFFIX),
kind: obj.kind,
namespace: obj.metadata.namespace
kind: obj.kind
}
})
@@ -67,25 +66,31 @@ 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)) {
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)) {
ingressEntityList.push(inputObject)
} 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 {
serviceEntityList: routedServiceEntityList,
serviceNameMap: serviceNameMap,
@@ -239,10 +234,9 @@ export function isServiceSelectorSubsetOfMatchLabel(
export async function fetchResource(
kubectl: Kubectl,
kind: string,
name: string,
namespace?: string
name: string
): Promise<K8sObject> {
const result = await kubectl.getResource(kind, name, false, namespace)
const result = await kubectl.getResource(kind, name)
if (result == null || !!result.stderr) {
return null
}
@@ -97,8 +97,7 @@ export async function validateIngresses(
const existingIngress = await fetchResource(
kubectl,
inputObject.kind,
inputObject.metadata.name,
inputObject?.metadata?.namespace
inputObject.metadata.name
)
const isValid =
@@ -31,8 +31,7 @@ export async function validateServicesState(
const existingService = await fetchResource(
kubectl,
serviceObject.kind,
serviceObject.metadata.name,
serviceObject?.metadata?.namespace
serviceObject.metadata.name
)
let isServiceGreen =
@@ -142,8 +142,7 @@ export async function validateTrafficSplitsState(
let trafficSplitObject = await fetchResource(
kubectl,
TRAFFIC_SPLIT_OBJECT,
getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX),
serviceObject?.metadata?.namespace
getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX)
)
core.debug(
`ts object extracted was ${JSON.stringify(trafficSplitObject)}`
@@ -184,8 +183,7 @@ export async function cleanupSMI(
serviceObject.metadata.name,
GREEN_SUFFIX
),
kind: serviceObject.kind,
namespace: serviceObject?.metadata?.namespace
kind: serviceObject.kind
})
})
+4 -9
View File
@@ -195,13 +195,9 @@ async function cleanUpCanary(
files: string[],
includeServices: boolean
): Promise<string[]> {
const deleteObject = async function (
kind: string,
name: string,
namespace: string | undefined
) {
const deleteObject = async function (kind, name) {
try {
const result = await kubectl.delete([kind, name], namespace)
const result = await kubectl.delete([kind, name])
checkForErrors([result])
} catch (ex) {
// Ignore failures of delete if it doesn't exist
@@ -217,7 +213,6 @@ 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) ||
@@ -227,8 +222,8 @@ async function cleanUpCanary(
const canaryObjectName = getCanaryResourceName(name)
const baselineObjectName = getBaselineResourceName(name)
await deleteObject(kind, canaryObjectName, namespace)
await deleteObject(kind, baselineObjectName, namespace)
await deleteObject(kind, canaryObjectName)
await deleteObject(kind, baselineObjectName)
}
}
}
+4 -20
View File
@@ -150,15 +150,8 @@ export async function annotateAndLabelResources(
resourceTypes: Resource[],
allPods: any
) {
const defaultWorkflowFileName = 'k8s-deploy-failed-workflow-annotation'
const githubToken = core.getInput('token')
let workflowFilePath
try {
workflowFilePath = await getWorkflowFilePath(githubToken)
} catch (ex) {
core.warning(`Failed to extract workflow file name: ${ex}`)
workflowFilePath = defaultWorkflowFileName
}
const workflowFilePath = await getWorkflowFilePath(githubToken)
const deploymentConfig = await getDeploymentConfig()
const annotationKeyLabel = getWorkflowAnnotationKeyLabel()
@@ -171,11 +164,8 @@ export async function annotateAndLabelResources(
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(
@@ -218,12 +208,7 @@ async function annotateResources(
)
if (annotateNamespace) {
annotateResults.push(
await kubectl.annotate(
'namespace',
namespace,
annotationKeyValStr,
namespace
)
await kubectl.annotate('namespace', namespace, annotationKeyValStr)
)
}
for (const file of files) {
@@ -248,7 +233,6 @@ async function annotateResources(
kubectl,
resource.type,
resource.name,
resource.namespace,
annotationKeyValStr,
allPods
)
@@ -274,7 +258,7 @@ async function labelResources(
const labelResults = []
for (const file of files) {
try {
const labelResult = await kubectl.labelFiles(file, labels)
const labelResult = await kubectl.labelFiles(files, labels)
labelResults.push(labelResult)
} catch (e) {
core.warning(`failed to annotate resource: ${e}`)
-2
View File
@@ -2,7 +2,6 @@ export interface K8sObject {
metadata: {
name: string
labels: Map<string, string>
namespace?: string
}
kind: string
spec: any
@@ -17,7 +16,6 @@ export interface K8sServiceObject extends K8sObject {
export interface K8sDeleteObject {
name: string
kind: string
namespace?: string
}
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 core from '@actions/core'
import * as toolCache from '@actions/tool-cache'
import {config} from 'process'
describe('Kubectl path', () => {
const version = '1.1'
@@ -37,7 +38,6 @@ 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,26 +122,6 @@ 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 () => {
@@ -160,26 +140,6 @@ 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 () => {
@@ -205,27 +165,6 @@ 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 () => {
@@ -246,22 +185,6 @@ 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 () => {
@@ -282,22 +205,6 @@ 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 () => {
@@ -318,21 +225,6 @@ 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 () => {
@@ -353,21 +245,6 @@ 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 () => {
@@ -396,20 +273,6 @@ 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 () => {
@@ -428,22 +291,6 @@ 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 () => {
@@ -474,14 +321,6 @@ 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 () => {
@@ -492,14 +331,6 @@ 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}
)
})
})
@@ -538,11 +369,5 @@ 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
})
})
})
+34 -60
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 = '',
namespace: string = 'default',
ignoreSSLErrors: boolean = false,
resourceGroup: string = '',
name: string = ''
@@ -47,7 +47,7 @@ export class Kubectl {
]
if (force) applyArgs.push('--force')
return await this.execute(applyArgs.concat(this.getFlags()))
return await this.execute(applyArgs)
} catch (err) {
core.debug('Kubectl apply failed:' + err)
}
@@ -56,43 +56,27 @@ export class Kubectl {
public async describe(
resourceType: string,
resourceName: string,
silent: boolean = false,
namespace?: string
silent: boolean = false
): Promise<ExecOutput> {
return await this.execute(
['describe', resourceType, resourceName].concat(
this.getFlags(namespace)
),
['describe', resourceType, resourceName],
silent
)
}
public async getNewReplicaSet(deployment: string, namespace?: string) {
const result = await this.describe(
'deployment',
deployment,
true,
namespace
)
public async getNewReplicaSet(deployment: string) {
const result = await this.describe('deployment', deployment, true)
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) {
core.debug(
`found string of interest for replicaset, line is ${line}`
)
core.debug(
`substring is ${line.substring(newreplicaset.length).trim()}`
)
if (line && line.toLowerCase().indexOf(newreplicaset) > -1)
newReplicaSet = line
.substring(newreplicaset.length)
.trim()
.split(' ')[0]
}
})
}
@@ -102,8 +86,7 @@ export class Kubectl {
public async annotate(
resourceType: string,
resourceName: string,
annotation: string,
namespace?: string
annotation: string
): Promise<ExecOutput> {
const args = [
'annotate',
@@ -111,14 +94,13 @@ export class Kubectl {
resourceName,
annotation,
'--overwrite'
].concat(this.getFlags(namespace))
]
return await this.execute(args)
}
public async annotateFiles(
files: string | string[],
annotation: string,
namespace?: string
annotation: string
): Promise<ExecOutput> {
const filesToAnnotate = createInlineArray(files)
core.debug(`annotating ${filesToAnnotate} with annotation ${annotation}`)
@@ -128,14 +110,16 @@ export class Kubectl {
filesToAnnotate,
annotation,
'--overwrite'
].concat(this.getFlags(namespace))
]
core.debug(
`sending args from annotate to execute: ${JSON.stringify(args)}`
)
return await this.execute(args)
}
public async labelFiles(
files: string | string[],
labels: string[],
namespace?: string
labels: string[]
): Promise<ExecOutput> {
const args = [
'label',
@@ -143,59 +127,51 @@ 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'].concat(this.getFlags()),
true
)
return await this.execute(['get', 'pods', '-o', 'json'], true)
}
public async checkRolloutStatus(
resourceType: string,
name: string,
namespace?: string
name: string
): Promise<ExecOutput> {
return await this.execute(
['rollout', 'status', `${resourceType}/${name}`].concat(
this.getFlags(namespace)
)
)
return await this.execute([
'rollout',
'status',
`${resourceType}/${name}`
])
}
public async getResource(
resourceType: string,
name: string,
silentFailure: boolean = false,
namespace?: string
silentFailure: boolean = false
): Promise<ExecOutput> {
core.debug(
'fetching resource of type ' + resourceType + ' and name ' + name
)
return await this.execute(
['get', `${resourceType}/${name}`, '-o', 'json'].concat(
this.getFlags(namespace)
),
['get', `${resourceType}/${name}`, '-o', 'json'],
silentFailure
)
}
public executeCommand(command: string, args?: string) {
if (!command) throw new Error('Command must be defined')
const a = args ? [args] : []
return this.execute([command, ...a.concat(this.getFlags())])
return args ? this.execute([command, args]) : this.execute([command])
}
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))])
public delete(args: string | string[]) {
if (typeof args === 'string') return this.execute(['delete', args])
return this.execute(['delete', ...args])
}
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, {
@@ -203,15 +179,13 @@ export class Kubectl {
})
}
protected getFlags(namespaceOverride?: string): string[] {
protected getExecuteFlags(): string[] {
const flags = []
if (this.ignoreSSLErrors) {
flags.push('--insecure-skip-tls-verify')
}
const ns = namespaceOverride || this.namespace
if (ns) {
flags.push('--namespace', ns)
if (this.namespace) {
flags.push('--namespace', this.namespace)
}
return flags
+1 -21
View File
@@ -1,32 +1,12 @@
import {PrivateKubectl} from './privatekubectl'
import * as exec from '@actions/exec'
describe('Private kubectl', () => {
const testString = `kubectl annotate -f test.yml,test2.yml,test3.yml -f test4.yml --filename test5.yml actions.github.com/k8s-deploy={"run":"3498366832","repository":"jaiveerk/k8s-deploy","workflow":"Minikube Integration Tests - private cluster","workflowFileName":"run-integration-tests-private.yml","jobName":"run-integration-test","createdBy":"jaiveerk","runUri":"https://github.com/jaiveerk/k8s-deploy/actions/runs/3498366832","commit":"c63b323186ea1320a31290de6dcc094c06385e75","lastSuccessRunCommit":"NA","branch":"refs/heads/main","deployTimestamp":1668787848577,"dockerfilePaths":{"nginx:1.14.2":""},"manifestsPaths":["https://github.com/jaiveerk/k8s-deploy/blob/c63b323186ea1320a31290de6dcc094c06385e75/test/integration/manifests/test.yml"],"helmChartPaths":[],"provider":"GitHub"} --overwrite --namespace test-3498366832`
const mockKube = new PrivateKubectl(
'kubectlPath',
'namespace',
true,
'resourceGroup',
'resourceName'
)
const mockKube = new PrivateKubectl('')
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}`
)
)
})
})
+3 -8
View File
@@ -8,6 +8,8 @@ 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
@@ -73,18 +75,11 @@ 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 (runObj.exitCode !== 0) {
if (runOutput.exitCode !== 0 && runObj.exitCode !== 0) {
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 {Kubectl} from '../types/kubectl'
const NAMESPACE = 'namespace'
export function checkForErrors(
execResults: ExecOutput[],
warnIfError?: boolean
@@ -32,12 +30,7 @@ export async function getLastSuccessfulRunSha(
annotationKey: string
): Promise<string> {
try {
const result = await kubectl.getResource(
NAMESPACE,
namespaceName,
false,
namespaceName
)
const result = await kubectl.getResource('namespace', namespaceName)
if (result?.stderr) {
core.warning(result.stderr)
return process.env.GITHUB_SHA
@@ -60,13 +53,12 @@ export async function annotateChildPods(
kubectl: Kubectl,
resourceType: string,
resourceName: string,
namespace: string | undefined,
annotationKeyValStr: string,
allPods
): Promise<ExecOutput[]> {
let owner = resourceName
if (resourceType.toLowerCase().indexOf('deployment') > -1) {
owner = await kubectl.getNewReplicaSet(resourceName, namespace)
owner = await kubectl.getNewReplicaSet(resourceName)
}
const commandExecutionResults = []
@@ -80,8 +72,7 @@ export async function annotateChildPods(
kubectl.annotate(
'pod',
pod.metadata.name,
annotationKeyValStr,
namespace
annotationKeyValStr
)
)
break
+28 -51
View File
@@ -4,9 +4,6 @@ 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[]
@@ -23,35 +20,24 @@ export async function checkManifestStability(
try {
const result = await kubectl.checkRolloutStatus(
resource.type,
resource.name,
resource.namespace
resource.name
)
checkForErrors([result])
} catch (ex) {
core.error(ex)
await kubectl.describe(
resource.type,
resource.name,
IS_SILENT,
resource.namespace
)
await kubectl.describe(resource.type, resource.name)
rolloutStatusHasErrors = true
}
}
if (resource.type == KubernetesConstants.KubernetesWorkload.POD) {
try {
await checkPodStatus(kubectl, resource)
await checkPodStatus(kubectl, resource.name)
} catch (ex) {
core.warning(
`Could not determine pod status: ${JSON.stringify(ex)}`
)
await kubectl.describe(
resource.type,
resource.name,
IS_SILENT,
resource.namespace
)
await kubectl.describe(resource.type, resource.name)
}
}
if (
@@ -59,11 +45,14 @@ export async function checkManifestStability(
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE
) {
try {
const service = await getService(kubectl, resource)
const service = await getService(kubectl, resource.name)
const {spec, status} = service
if (spec.type === KubernetesConstants.ServiceTypes.LOAD_BALANCER) {
if (!isLoadBalancerIPAssigned(status)) {
await waitForServiceExternalIPAssignment(kubectl, resource)
await waitForServiceExternalIPAssignment(
kubectl,
resource.name
)
} else {
core.info(
`ServiceExternalIP ${resource.name} ${status.loadBalancer.ingress[0].ip}`
@@ -74,12 +63,7 @@ export async function checkManifestStability(
core.warning(
`Could not determine service status of: ${resource.name} Error: ${ex}`
)
await kubectl.describe(
resource.type,
resource.name,
IS_SILENT,
resource.namespace
)
await kubectl.describe(resource.type, resource.name)
}
}
}
@@ -91,7 +75,7 @@ export async function checkManifestStability(
export async function checkPodStatus(
kubectl: Kubectl,
pod: Resource
podName: string
): Promise<void> {
const sleepTimeout = 10 * 1000 // 10 seconds
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++) {
await sleep(sleepTimeout)
core.debug(`Polling for pod status: ${pod.name}`)
podStatus = await getPodStatus(kubectl, pod)
core.debug(`Polling for pod status: ${podName}`)
podStatus = await getPodStatus(kubectl, podName)
if (
podStatus &&
@@ -113,42 +97,37 @@ export async function checkPodStatus(
}
}
podStatus = await getPodStatus(kubectl, pod)
podStatus = await getPodStatus(kubectl, podName)
switch (podStatus.phase) {
case 'Succeeded':
case 'Running':
if (isPodReady(podStatus)) {
console.log(`pod/${pod.name} is successfully rolled out`)
console.log(`pod/${podName} is successfully rolled out`)
} else {
kubectlDescribeNeeded = true
}
break
case 'Pending':
if (!isPodReady(podStatus)) {
core.warning(`pod/${pod.name} rollout status check timed out`)
core.warning(`pod/${podName} rollout status check timed out`)
kubectlDescribeNeeded = true
}
break
case 'Failed':
core.error(`pod/${pod.name} rollout failed`)
core.error(`pod/${podName} rollout failed`)
kubectlDescribeNeeded = true
break
default:
core.warning(`pod/${pod.name} rollout status: ${podStatus.phase}`)
core.warning(`pod/${podName} rollout status: ${podStatus.phase}`)
}
if (kubectlDescribeNeeded) {
await kubectl.describe(POD, pod.name, IS_SILENT, pod.namespace)
await kubectl.describe('pod', podName)
}
}
async function getPodStatus(kubectl: Kubectl, pod: Resource) {
const podResult = await kubectl.getResource(
POD,
pod.name,
IS_SILENT,
pod.namespace
)
async function getPodStatus(kubectl: Kubectl, podName: string) {
const podResult = await kubectl.getResource('pod', podName)
checkForErrors([podResult])
return JSON.parse(podResult.stdout).status
@@ -172,12 +151,10 @@ function isPodReady(podStatus: any): boolean {
return allContainersAreReady
}
async function getService(kubectl: Kubectl, service: Resource) {
async function getService(kubectl: Kubectl, serviceName) {
const serviceResult = await kubectl.getResource(
KubernetesConstants.DiscoveryAndLoadBalancerResource.SERVICE,
service.name,
IS_SILENT,
service.namespace
serviceName
)
checkForErrors([serviceResult])
@@ -186,25 +163,25 @@ async function getService(kubectl: Kubectl, service: Resource) {
async function waitForServiceExternalIPAssignment(
kubectl: Kubectl,
service: Resource
serviceName: string
): 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 : ${service.name}`)
core.info(`Wait for service ip assignment : ${serviceName}`)
await sleep(sleepTimeout)
const status = (await getService(kubectl, service)).status
const status = (await getService(kubectl, serviceName)).status
if (isLoadBalancerIPAssigned(status)) {
core.info(
`ServiceExternalIP ${service.name} ${status.loadBalancer.ingress[0].ip}`
`ServiceExternalIP ${serviceName} ${status.loadBalancer.ingress[0].ip}`
)
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) {
+1 -2
View File
@@ -280,8 +280,7 @@ export function getResources(
) {
resources.push({
type: inputObject.kind,
name: inputObject.metadata.name,
namespace: inputObject?.metadata?.namespace
name: inputObject.metadata.name
})
}
})
+1 -14
View File
@@ -1,8 +1,4 @@
import {
cleanLabel,
removeInvalidLabelCharacters,
VALID_LABEL_REGEX
} from '../utilities/workflowAnnotationUtils'
import {cleanLabel} from '../utilities/workflowAnnotationUtils'
describe('WorkflowAnnotationUtils', () => {
describe('cleanLabel', () => {
@@ -20,14 +16,5 @@ 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)
})
})
})
+4 -12
View File
@@ -2,8 +2,6 @@ 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,
@@ -39,17 +37,11 @@ export function getWorkflowAnnotationKeyLabel(): string {
* @returns cleaned label
*/
export function cleanLabel(label: string): string {
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
let removedInvalidChars = 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] || ''
}