Compare commits

..

1 Commits

Author SHA1 Message Date
Oliver King ebc294c887 add clean function 2022-07-06 16:07:01 -04:00
27 changed files with 2112 additions and 3066 deletions
-28
View File
@@ -1,28 +0,0 @@
name: Bug Report
description: File a bug report, we will respond to this thread with any questions.
title: 'Bug: '
labels: ['bug', 'triage']
assignees: '@Azure/aks-atlanta'
body:
- type: input
id: What-happened
attributes:
label: What happened?
description: Tell us what happened and how is it different form the expected?
placeholder: Tell us what you see!
validations:
required: true
- type: input
id: Runner
attributes:
label: Runner
description: What runner are you using?
placeholder: Mention the runner info (self-hosted, operating system)
validations:
required: true
- type: input
id: Logs
attributes:
label: Relevant log output
description: Run in debug mode for the most verbose logs. Please feel free to attach a screenshot of the logs
render: shell
-6
View File
@@ -1,6 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: GitHub Action "aks-set-context" Support
url: https://github.com/Azure/aks-set-context
security: https://github.com/Azure/aks-set-context/blob/main/SECURITY.md
about: Please ask and answer questions here.
@@ -1,13 +0,0 @@
name: Feature Request
description: File a Feature Request form, we will respond to this thread with any questions.
title: 'Feature Request: '
labels: ['Feature']
assignees: '@Azure/aks-atlanta'
body:
- type: input
id: Feature request
attributes:
label: Feature request
description: Provide example functionality and links to relevant docs
validations:
required: true
-27
View File
@@ -74,9 +74,6 @@ Following are the key capabilities of this action:
<td>traffic-split-method </br></br>(Optional)</td> <td>traffic-split-method </br></br>(Optional)</td>
<td>Acceptable values: pod/smi.<br> Default value: pod <br>SMI: Percentage traffic split is done at request level using service mesh. Service mesh has to be setup by cluster admin. Orchestration of <a href="https://github.com/servicemeshinterface/smi-spec/blob/master/apis/traffic-split/v1alpha3/traffic-split.md" data-raw-source="TrafficSplit](https://github.com/deislabs/smi-spec/blob/master/traffic-split.md)">TrafficSplit</a> objects of SMI is handled by this action. <br>Pod: Percentage split not possible at request level in the absence of service mesh. Percentage input is used to calculate the replicas for baseline and canary as a percentage of replicas specified in the input manifests for the stable variant.</td> <td>Acceptable values: pod/smi.<br> Default value: pod <br>SMI: Percentage traffic split is done at request level using service mesh. Service mesh has to be setup by cluster admin. Orchestration of <a href="https://github.com/servicemeshinterface/smi-spec/blob/master/apis/traffic-split/v1alpha3/traffic-split.md" data-raw-source="TrafficSplit](https://github.com/deislabs/smi-spec/blob/master/traffic-split.md)">TrafficSplit</a> objects of SMI is handled by this action. <br>Pod: Percentage split not possible at request level in the absence of service mesh. Percentage input is used to calculate the replicas for baseline and canary as a percentage of replicas specified in the input manifests for the stable variant.</td>
</tr> </tr>
<tr>
<td>traffic-split-annotations </br></br>(Optional)</td>
<td>Annotations in the form of key/value pair to be added to TrafficSplit.</td>
<tr> <tr>
<td>percentage </br></br>(Optional but required if strategy is canary)</td> <td>percentage </br></br>(Optional but required if strategy is canary)</td>
<td>Used to compute the number of replicas of &#39;-baseline&#39; and &#39;-canary&#39; variants of the workloads found in manifest files. For the specified percentage input, if (percentage * numberOfDesirerdReplicas)/100 is not a round number, the floor of this number is used while creating &#39;-baseline&#39; and &#39;-canary&#39;.<br/><br/>For example, if Deployment hello-world was found in the input manifest file with &#39;replicas: 4&#39; and if &#39;strategy: canary&#39; and &#39;percentage: 25&#39; are given as inputs to the action, then the Deployments hello-world-baseline and hello-world-canary are created with 1 replica each. The &#39;-baseline&#39; variant is created with the same image and tag as the stable version (4 replica variant prior to deployment) while the &#39;-canary&#39; variant is created with the image and tag corresponding to the new changes being deployed</td> <td>Used to compute the number of replicas of &#39;-baseline&#39; and &#39;-canary&#39; variants of the workloads found in manifest files. For the specified percentage input, if (percentage * numberOfDesirerdReplicas)/100 is not a round number, the floor of this number is used while creating &#39;-baseline&#39; and &#39;-canary&#39;.<br/><br/>For example, if Deployment hello-world was found in the input manifest file with &#39;replicas: 4&#39; and if &#39;strategy: canary&#39; and &#39;percentage: 25&#39; are given as inputs to the action, then the Deployments hello-world-baseline and hello-world-canary are created with 1 replica each. The &#39;-baseline&#39; variant is created with the same image and tag as the stable version (4 replica variant prior to deployment) while the &#39;-canary&#39; variant is created with the image and tag corresponding to the new changes being deployed</td>
@@ -96,10 +93,6 @@ Following are the key capabilities of this action:
<td>version-switch-buffer </br></br>(Optional and relevant only if strategy is blue-green)</td> <td>version-switch-buffer </br></br>(Optional and relevant only if strategy is blue-green)</td>
<td>Acceptable values: 1-300.</br>Default value: 0.</br>Waits for the given input in minutes before routing traffic to '-green' workloads.</td> <td>Acceptable values: 1-300.</br>Default value: 0.</br>Waits for the given input in minutes before routing traffic to '-green' workloads.</td>
</tr> </tr>
<tr>
<td>private-cluster </br></br>(Optional and relevant only using K8's deploy for a cluster with private cluster enabled)</td>
<td>Acceptable values: true, false</br>Default value: false.</td>
</tr>
<tr> <tr>
<td>force </br></br>(Optional)</td> <td>force </br></br>(Optional)</td>
<td>Deploy when a previous deployment already exists. If true then '--force' argument is added to the apply command. Using '--force' argument is not recommended in production.</td> <td>Deploy when a previous deployment already exists. If true then '--force' argument is added to the apply command. Using '--force' argument is not recommended in production.</td>
@@ -126,26 +119,6 @@ Following are the key capabilities of this action:
image-pull-secret2 image-pull-secret2
``` ```
### Private cluster deployment
```yaml
- uses: Azure/k8s-deploy@v4
with:
resource-group: yourResourceGroup
name: yourClusterName
action: deploy
strategy: basic
private-cluster: true
manifests: |
manifests/azure-vote-backend-deployment.yaml
manifests/azure-vote-backend-service.yaml
manifests/azure-vote-frontend-deployment.yaml
manifests/azure-vote-frontend-service.yaml
images: |
registry.azurecr.io/containername
```
### Canary deployment without service mesh ### Canary deployment without service mesh
```yaml ```yaml
-13
View File
@@ -35,9 +35,6 @@ inputs:
description: 'Traffic split method to be used. Allowed values are pod and smi' description: 'Traffic split method to be used. Allowed values are pod and smi'
required: false required: false
default: 'pod' default: 'pod'
traffic-split-annotations:
description: 'Annotations in the form of key/value pair to be added to TrafficSplit. Relevant only if deployement strategy is blue-green or canary'
required: false
baseline-and-canary-replicas: baseline-and-canary-replicas:
description: 'Baseline and canary replicas count. Valid value between 0 to 100 (inclusive)' description: 'Baseline and canary replicas count. Valid value between 0 to 100 (inclusive)'
required: false required: false
@@ -62,16 +59,6 @@ inputs:
description: 'Annotate the target namespace' description: 'Annotate the target namespace'
required: false required: false
default: true default: true
private-cluster:
description: 'True if cluster is AKS private cluster'
required: false
default: false
resource-group:
description: 'Name of resource group - Only required if using private cluster'
required: false
name:
description: 'Resource group name - Only required if using private cluster'
required: false
branding: branding:
color: 'green' color: 'green'
+1984 -2400
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -23,8 +23,9 @@
"@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.34.0",
"jest": "^26.0.0", "jest": "^26.0.0",
"prettier": "^2.7.1", "prettier": "2.7.1",
"ts-jest": "^26.0.0", "ts-jest": "^26.0.0",
"typescript": "3.9.5" "typescript": "3.9.5"
} }
+2 -4
View File
@@ -19,8 +19,7 @@ import {parseRouteStrategy} from '../types/routeStrategy'
export async function deploy( export async function deploy(
kubectl: Kubectl, kubectl: Kubectl,
manifestFilePaths: string[], manifestFilePaths: string[],
deploymentStrategy: DeploymentStrategy, deploymentStrategy: DeploymentStrategy
annotations: {[key: string]: string} = {}
) { ) {
// update manifests // update manifests
const inputManifestFiles: string[] = updateManifestFiles(manifestFilePaths) const inputManifestFiles: string[] = updateManifestFiles(manifestFilePaths)
@@ -35,8 +34,7 @@ export async function deploy(
inputManifestFiles, inputManifestFiles,
deploymentStrategy, deploymentStrategy,
kubectl, kubectl,
trafficSplitMethod, trafficSplitMethod
annotations
) )
core.endGroup() core.endGroup()
core.debug('Deployed manifest files: ' + deployedManifestFiles) core.debug('Deployed manifest files: ' + deployedManifestFiles)
+4 -10
View File
@@ -40,15 +40,14 @@ import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy'
export async function promote( export async function promote(
kubectl: Kubectl, kubectl: Kubectl,
manifests: string[], manifests: string[],
deploymentStrategy: DeploymentStrategy, deploymentStrategy: DeploymentStrategy
annotations: {[key: string]: string} = {}
) { ) {
switch (deploymentStrategy) { switch (deploymentStrategy) {
case DeploymentStrategy.CANARY: case DeploymentStrategy.CANARY:
await promoteCanary(kubectl, manifests) await promoteCanary(kubectl, manifests)
break break
case DeploymentStrategy.BLUE_GREEN: case DeploymentStrategy.BLUE_GREEN:
await promoteBlueGreen(kubectl, manifests, annotations) await promoteBlueGreen(kubectl, manifests)
break break
default: default:
throw Error('Invalid promote deployment strategy') throw Error('Invalid promote deployment strategy')
@@ -105,11 +104,7 @@ async function promoteCanary(kubectl: Kubectl, manifests: string[]) {
core.endGroup() core.endGroup()
} }
async function promoteBlueGreen( async function promoteBlueGreen(kubectl: Kubectl, manifests: string[]) {
kubectl: Kubectl,
manifests: string[],
annotations: {[key: string]: string} = {}
) {
// update container images and pull secrets // update container images and pull secrets
const inputManifestFiles: string[] = updateManifestFiles(manifests) const inputManifestFiles: string[] = updateManifestFiles(manifests)
const manifestObjects: BlueGreenManifests = const manifestObjects: BlueGreenManifests =
@@ -162,8 +157,7 @@ async function promoteBlueGreen(
await routeBlueGreenSMI( await routeBlueGreenSMI(
kubectl, kubectl,
NONE_LABEL_VALUE, NONE_LABEL_VALUE,
manifestObjects.serviceEntityList, manifestObjects.serviceEntityList
annotations
) )
await deleteWorkloadsWithLabel( await deleteWorkloadsWithLabel(
kubectl, kubectl,
+4 -9
View File
@@ -15,15 +15,14 @@ import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy'
export async function reject( export async function reject(
kubectl: Kubectl, kubectl: Kubectl,
manifests: string[], manifests: string[],
deploymentStrategy: DeploymentStrategy, deploymentStrategy: DeploymentStrategy
annotations: {[key: string]: string} = {}
) { ) {
switch (deploymentStrategy) { switch (deploymentStrategy) {
case DeploymentStrategy.CANARY: case DeploymentStrategy.CANARY:
await rejectCanary(kubectl, manifests) await rejectCanary(kubectl, manifests)
break break
case DeploymentStrategy.BLUE_GREEN: case DeploymentStrategy.BLUE_GREEN:
await rejectBlueGreen(kubectl, manifests, annotations) await rejectBlueGreen(kubectl, manifests)
break break
default: default:
throw 'Invalid delete deployment strategy' throw 'Invalid delete deployment strategy'
@@ -55,11 +54,7 @@ async function rejectCanary(kubectl: Kubectl, manifests: string[]) {
core.endGroup() core.endGroup()
} }
async function rejectBlueGreen( async function rejectBlueGreen(kubectl: Kubectl, manifests: string[]) {
kubectl: Kubectl,
manifests: string[],
annotations: {[key: string]: string} = {}
) {
core.startGroup('Rejecting deployment with blue green strategy') core.startGroup('Rejecting deployment with blue green strategy')
const routeStrategy = parseRouteStrategy( const routeStrategy = parseRouteStrategy(
@@ -68,7 +63,7 @@ async function rejectBlueGreen(
if (routeStrategy == RouteStrategy.INGRESS) { if (routeStrategy == RouteStrategy.INGRESS) {
await rejectBlueGreenIngress(kubectl, manifests) await rejectBlueGreenIngress(kubectl, manifests)
} else if (routeStrategy == RouteStrategy.SMI) { } else if (routeStrategy == RouteStrategy.SMI) {
await rejectBlueGreenSMI(kubectl, manifests, annotations) await rejectBlueGreenSMI(kubectl, manifests)
} else { } else {
await rejectBlueGreenService(kubectl, manifests) await rejectBlueGreenService(kubectl, manifests)
} }
+5 -22
View File
@@ -6,8 +6,6 @@ import {reject} from './actions/reject'
import {Action, parseAction} from './types/action' import {Action, parseAction} from './types/action'
import {parseDeploymentStrategy} from './types/deploymentStrategy' import {parseDeploymentStrategy} from './types/deploymentStrategy'
import {getFilesFromDirectories} from './utilities/fileUtils' import {getFilesFromDirectories} from './utilities/fileUtils'
import {PrivateKubectl} from './types/privatekubectl'
import {parseAnnotations} from './types/annotations'
export async function run() { export async function run() {
// verify kubeconfig is set // verify kubeconfig is set
@@ -20,9 +18,6 @@ export async function run() {
const action: Action | undefined = parseAction( const action: Action | undefined = parseAction(
core.getInput('action', {required: true}) core.getInput('action', {required: true})
) )
const annotations = parseAnnotations(
core.getInput('annotations', {required: false})
)
const strategy = parseDeploymentStrategy(core.getInput('strategy')) const strategy = parseDeploymentStrategy(core.getInput('strategy'))
const manifestsInput = core.getInput('manifests', {required: true}) const manifestsInput = core.getInput('manifests', {required: true})
const manifestFilePaths = manifestsInput const manifestFilePaths = manifestsInput
@@ -31,35 +26,23 @@ export async function run() {
.filter((manifest) => manifest.length > 0) // remove any blanks .filter((manifest) => manifest.length > 0) // remove any blanks
const fullManifestFilePaths = getFilesFromDirectories(manifestFilePaths) const fullManifestFilePaths = getFilesFromDirectories(manifestFilePaths)
// create kubectl
const kubectlPath = await getKubectlPath() const kubectlPath = await getKubectlPath()
const namespace = core.getInput('namespace') || 'default' const namespace = core.getInput('namespace') || 'default'
const isPrivateCluster = const kubectl = new Kubectl(kubectlPath, namespace, true)
core.getInput('private-cluster').toLowerCase() === 'true'
const resourceGroup = core.getInput('resource-group') || ''
const resourceName = core.getInput('name') || ''
const kubectl = isPrivateCluster
? new PrivateKubectl(
kubectlPath,
namespace,
true,
resourceGroup,
resourceName
)
: new Kubectl(kubectlPath, namespace, true)
// run action // run action
switch (action) { switch (action) {
case Action.DEPLOY: { case Action.DEPLOY: {
await deploy(kubectl, fullManifestFilePaths, strategy, annotations) await deploy(kubectl, fullManifestFilePaths, strategy)
break break
} }
case Action.PROMOTE: { case Action.PROMOTE: {
await promote(kubectl, fullManifestFilePaths, strategy, annotations) await promote(kubectl, fullManifestFilePaths, strategy)
break break
} }
case Action.REJECT: { case Action.REJECT: {
await reject(kubectl, fullManifestFilePaths, strategy, annotations) await reject(kubectl, fullManifestFilePaths, strategy)
break break
} }
default: { default: {
@@ -1,128 +0,0 @@
import {
createWorkloadsWithLabel,
deleteWorkloadsAndServicesWithLabel,
getManifestObjects,
getNewBlueGreenObject,
GREEN_LABEL_VALUE,
isServiceRouted,
NONE_LABEL_VALUE
} from './blueGreenHelper'
import * as bgHelper from './blueGreenHelper'
import {Kubectl} from '../../types/kubectl'
import * as fileHelper from '../../utilities/fileUtils'
jest.mock('../../types/kubectl')
describe('bluegreenhelper functions', () => {
let testObjects
beforeEach(() => {
//@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(['test/unit/manifests/test-ingress.yml'])
jest
.spyOn(fileHelper, 'writeObjectsToFile')
.mockImplementationOnce(() => [''])
})
test('it should parse objects correctly from one file', () => {
expect(testObjects.deploymentEntityList[0].kind).toBe('Deployment')
expect(testObjects.serviceEntityList[0].kind).toBe('Service')
expect(testObjects.ingressEntityList[0].kind).toBe('Ingress')
expect(
testObjects.deploymentEntityList[0].spec.selector.matchLabels.app
).toBe('nginx')
})
test('correctly makes new blue green object', () => {
const modifiedDeployment = getNewBlueGreenObject(
testObjects.deploymentEntityList[0],
GREEN_LABEL_VALUE
)
//@ts-ignore
expect(modifiedDeployment.metadata.name).toBe('nginx-deployment-green')
//@ts-ignore
expect(modifiedDeployment.metadata.labels['k8s.deploy.color']).toBe(
'green'
)
const modifiedSvc = getNewBlueGreenObject(
testObjects.serviceEntityList[0],
GREEN_LABEL_VALUE
)
//@ts-ignore
expect(modifiedSvc.metadata.name).toBe('nginx-service-green')
//@ts-ignore
expect(modifiedSvc.metadata.labels['k8s.deploy.color']).toBe('green')
})
test('correctly makes labeled workloads', () => {
const kubectl = new Kubectl('')
expect(Kubectl).toBeCalledTimes(1)
const cwlResult = createWorkloadsWithLabel(
kubectl,
testObjects.deploymentEntityList,
GREEN_LABEL_VALUE
)
cwlResult.then((value) => {
//@ts-ignore
expect(value.newFilePaths[0]).toBe('')
})
})
test('correctly classifies routed services', () => {
expect(
isServiceRouted(
testObjects.serviceEntityList[0],
testObjects.deploymentEntityList
)
).toBe(true)
testObjects.serviceEntityList[0].spec.selector.app = 'fakeapp'
expect(
isServiceRouted(
testObjects.serviceEntityList[0],
testObjects.deploymentEntityList
)
).toBe(false)
})
test('correctly deletes services and workloads according to label', () => {
const kubectl = new Kubectl('')
jest.spyOn(bgHelper, 'deleteObjects').mockReturnValue({} as Promise<void>)
let objectsToDelete = deleteWorkloadsAndServicesWithLabel(
kubectl,
NONE_LABEL_VALUE,
testObjects.deploymentEntityList,
testObjects.serviceEntityList
)
objectsToDelete.then((value) => {
expect(value).toHaveLength(2)
expect(value).toContainEqual
;({name: 'nginx-service', kind: 'Service'})
expect(value).toContainEqual({
name: 'nginx-deployment',
kind: 'Deployment'
})
})
objectsToDelete = deleteWorkloadsAndServicesWithLabel(
kubectl,
GREEN_LABEL_VALUE,
testObjects.deploymentEntityList,
testObjects.serviceEntityList
)
objectsToDelete.then((value) => {
expect(value).toHaveLength(2)
expect(value).toContainEqual({
name: 'nginx-service-green',
kind: 'Service'
})
expect(value).toContainEqual({
name: 'nginx-deployment-green',
kind: 'Deployment'
})
})
})
})
@@ -40,8 +40,7 @@ export interface BlueGreenManifests {
export async function routeBlueGreen( export async function routeBlueGreen(
kubectl: Kubectl, kubectl: Kubectl,
inputManifestFiles: string[], inputManifestFiles: string[],
routeStrategy: RouteStrategy, routeStrategy: RouteStrategy
annotations: {[key: string]: string} = {}
) { ) {
// sleep for buffer time // sleep for buffer time
const bufferTime: number = parseInt( const bufferTime: number = parseInt(
@@ -75,8 +74,7 @@ export async function routeBlueGreen(
await routeBlueGreenSMI( await routeBlueGreenSMI(
kubectl, kubectl,
GREEN_LABEL_VALUE, GREEN_LABEL_VALUE,
manifestObjects.serviceEntityList, manifestObjects.serviceEntityList
annotations
) )
} else { } else {
await routeBlueGreenService( await routeBlueGreenService(
@@ -112,7 +110,6 @@ export async function deleteWorkloadsWithLabel(
}) })
await deleteObjects(kubectl, resourcesToDelete) await deleteObjects(kubectl, resourcesToDelete)
return resourcesToDelete
} }
export async function deleteWorkloadsAndServicesWithLabel( export async function deleteWorkloadsAndServicesWithLabel(
@@ -144,7 +141,6 @@ export async function deleteWorkloadsAndServicesWithLabel(
}) })
await deleteObjects(kubectl, resourcesToDelete) await deleteObjects(kubectl, resourcesToDelete)
return resourcesToDelete
} }
export async function deleteObjects(kubectl: Kubectl, deleteList: any[]) { export async function deleteObjects(kubectl: Kubectl, deleteList: any[]) {
@@ -239,6 +235,9 @@ export async function createWorkloadsWithLabel(
deploymentObjectList.forEach((inputObject) => { deploymentObjectList.forEach((inputObject) => {
// creating deployment with label // creating deployment with label
const newBlueGreenObject = getNewBlueGreenObject(inputObject, nextLabel) const newBlueGreenObject = getNewBlueGreenObject(inputObject, nextLabel)
core.debug(
'New blue-green object is: ' + JSON.stringify(newBlueGreenObject)
)
newObjectsList.push(newBlueGreenObject) newObjectsList.push(newBlueGreenObject)
}) })
@@ -279,7 +278,7 @@ export function addBlueGreenLabelsAndAnnotations(
updateObjectLabels(inputObject, newLabels, false) updateObjectLabels(inputObject, newLabels, false)
updateSelectorLabels(inputObject, newLabels, false) updateSelectorLabels(inputObject, newLabels, false)
// updating spec labels if it is not a service // updating spec labels if it is a service
if (!isServiceEntity(inputObject.kind)) { if (!isServiceEntity(inputObject.kind)) {
updateSpecLabels(inputObject, newLabels, false) updateSpecLabels(inputObject, newLabels, false)
} }
@@ -1,88 +0,0 @@
import {getManifestObjects, GREEN_LABEL_VALUE} from './blueGreenHelper'
import {
deployBlueGreenIngress,
getUpdatedBlueGreenIngress,
isIngressRouted,
routeBlueGreenIngress
} from './ingressBlueGreenHelper'
import {Kubectl} from '../../types/kubectl'
import * as fileHelper from '../../utilities/fileUtils'
jest.mock('../../types/kubectl')
describe('ingress blue green helpers', () => {
let testObjects
const betaFilepath = ['test/unit/manifests/test-ingress.yml']
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
beforeEach(() => {
//@ts-ignore
Kubectl.mockClear()
testObjects = getManifestObjects(ingressFilepath)
jest
.spyOn(fileHelper, 'writeObjectsToFile')
.mockImplementationOnce(() => [''])
})
test('it should correctly classify ingresses', () => {
expect(
isIngressRouted(
testObjects.ingressEntityList[0],
testObjects.serviceNameMap
)
).toBe(true)
testObjects.ingressEntityList[0].spec.rules[0].http.paths = {}
expect(
isIngressRouted(
testObjects.ingressEntityList[0],
testObjects.serviceNameMap
)
).toBe(false)
expect(
isIngressRouted(
getManifestObjects(betaFilepath).ingressEntityList[0],
testObjects.serviceNameMap
)
).toBe(true)
})
test('it should correctly update ingresses', () => {
const updatedIng = getUpdatedBlueGreenIngress(
testObjects.ingressEntityList[0],
testObjects.serviceNameMap,
GREEN_LABEL_VALUE
)
//@ts-ignore
expect(updatedIng.metadata.labels['k8s.deploy.color']).toBe('green')
//@ts-ignore
expect(updatedIng.spec.rules[0].http.paths[0].backend.service.name).toBe(
'nginx-service-green'
)
})
test('correctly prepares blue/green ingresses for deployment', () => {
const kc = new Kubectl('')
const generatedObjects = routeBlueGreenIngress(
kc,
GREEN_LABEL_VALUE,
testObjects.serviceNameMap,
testObjects.ingressEntityList
)
generatedObjects.then((value) => {
expect(value).toHaveLength(1)
//@ts-ignore
expect(value[0].metadata.name).toBe('nginx-ingress')
})
})
test('correctly deploys services', () => {
const kc = new Kubectl('')
const result = deployBlueGreenIngress(kc, ingressFilepath)
result.then((value) => {
const nol = value.newObjectsList
//@ts-ignore
expect(nol[0].metadata.name).toBe('nginx-service-green')
})
})
})
@@ -14,7 +14,7 @@ import {
} from './blueGreenHelper' } from './blueGreenHelper'
import * as core from '@actions/core' import * as core from '@actions/core'
const BACKEND = 'backend' const BACKEND = 'BACKEND'
export async function deployBlueGreenIngress( export async function deployBlueGreenIngress(
kubectl: Kubectl, kubectl: Kubectl,
@@ -24,12 +24,13 @@ export async function deployBlueGreenIngress(
const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths) const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths)
// create deployments with green label value // create deployments with green label value
const workloadDeployment = await createWorkloadsWithLabel( const result = createWorkloadsWithLabel(
kubectl, kubectl,
manifestObjects.deploymentEntityList, manifestObjects.deploymentEntityList,
GREEN_LABEL_VALUE GREEN_LABEL_VALUE
) )
// create new services and other objects
let newObjectsList = [] let newObjectsList = []
manifestObjects.serviceEntityList.forEach((inputObject) => { manifestObjects.serviceEntityList.forEach((inputObject) => {
const newBlueGreenObject = getNewBlueGreenObject( const newBlueGreenObject = getNewBlueGreenObject(
@@ -45,12 +46,7 @@ export async function deployBlueGreenIngress(
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList) const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList)
await kubectl.apply(manifestFiles) await kubectl.apply(manifestFiles)
core.debug( return result
'new objects after processing services and other objects: \n' +
JSON.stringify(newObjectsList)
)
return {workloadDeployment, newObjectsList}
} }
export async function promoteBlueGreenIngress( export async function promoteBlueGreenIngress(
@@ -58,13 +54,14 @@ export async function promoteBlueGreenIngress(
manifestObjects manifestObjects
) { ) {
//checking if anything to promote //checking if anything to promote
const {areValid, invalidIngresses} = validateIngresses( if (
kubectl, !validateIngressesState(
manifestObjects.ingressEntityList, kubectl,
manifestObjects.serviceNameMap manifestObjects.ingressEntityList,
) manifestObjects.serviceNameMap
if (!areValid) { )
throw 'Ingresses are not in promote state' + invalidIngresses.toString() ) {
throw 'Ingress not in promote state'
} }
// create stable deployments with new configuration // create stable deployments with new configuration
@@ -141,18 +138,17 @@ export async function routeBlueGreenIngress(
}) })
} }
core.debug('New objects: ' + JSON.stringify(newObjectsList))
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList) const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList)
await kubectl.apply(manifestFiles) await kubectl.apply(manifestFiles)
return newObjectsList
} }
export function validateIngresses( export function validateIngressesState(
kubectl: Kubectl, kubectl: Kubectl,
ingressEntityList: any[], ingressEntityList: any[],
serviceNameMap: Map<string, string> serviceNameMap: Map<string, string>
): {areValid: boolean; invalidIngresses: string[]} { ): boolean {
let areValid: boolean = true let areIngressesTargetingNewServices: boolean = true
const invalidIngresses = []
ingressEntityList.forEach(async (inputObject) => { ingressEntityList.forEach(async (inputObject) => {
if (isIngressRouted(inputObject, serviceNameMap)) { if (isIngressRouted(inputObject, serviceNameMap)) {
//querying existing ingress //querying existing ingress
@@ -162,32 +158,33 @@ export function validateIngresses(
inputObject.metadata.name inputObject.metadata.name
) )
let isValid = if (!!existingIngress) {
!!existingIngress && const currentLabel: string =
existingIngress?.metadata?.labels[BLUE_GREEN_VERSION_LABEL] === existingIngress?.metadata?.labels[BLUE_GREEN_VERSION_LABEL]
GREEN_LABEL_VALUE
if (!isValid) { // if not green label, then wrong configuration
invalidIngresses.push(inputObject.metadata.name) if (currentLabel != GREEN_LABEL_VALUE)
areIngressesTargetingNewServices = false
} else {
// no ingress at all, so nothing to promote
areIngressesTargetingNewServices = false
} }
// to be valid, ingress should exist and should be green
areValid = areValid && isValid
} }
}) })
return {areValid, invalidIngresses} return areIngressesTargetingNewServices
} }
export function isIngressRouted( function isIngressRouted(
ingressObject: any, ingressObject: any,
serviceNameMap: Map<string, string> serviceNameMap: Map<string, string>
): boolean { ): boolean {
let isIngressRouted: boolean = false let isIngressRouted: boolean = false
// check if ingress targets a service in the given manifests // check if ingress targets a service in the given manifests
JSON.parse(JSON.stringify(ingressObject), (key, value) => { JSON.parse(JSON.stringify(ingressObject), (key, value) => {
isIngressRouted = if (key === 'serviceName' && serviceNameMap.has(value)) {
isIngressRouted || (key === 'service' && value.hasOwnProperty('name')) isIngressRouted = true
isIngressRouted = }
isIngressRouted || (key === 'serviceName' && serviceNameMap.has(value))
return value return value
}) })
@@ -209,18 +206,15 @@ export function getUpdatedBlueGreenIngress(
addBlueGreenLabelsAndAnnotations(newObject, type) addBlueGreenLabelsAndAnnotations(newObject, type)
// update ingress labels // update ingress labels
if (inputObject.apiVersion === 'networking.k8s.io/v1beta1') {
return updateIngressBackendBetaV1(newObject, serviceNameMap)
}
return updateIngressBackend(newObject, serviceNameMap) return updateIngressBackend(newObject, serviceNameMap)
} }
export function updateIngressBackendBetaV1( export function updateIngressBackend(
inputObject: any, inputObject: any,
serviceNameMap: Map<string, string> serviceNameMap: Map<string, string>
): any { ): any {
inputObject = JSON.parse(JSON.stringify(inputObject), (key, value) => { inputObject = JSON.parse(JSON.stringify(inputObject), (key, value) => {
if (key.toLowerCase() === BACKEND) { if (key.toUpperCase() === BACKEND) {
const {serviceName} = value const {serviceName} = value
if (serviceNameMap.has(serviceName)) { if (serviceNameMap.has(serviceName)) {
// update service name with corresponding bluegreen name only if service is provied in given manifests // update service name with corresponding bluegreen name only if service is provied in given manifests
@@ -233,20 +227,3 @@ export function updateIngressBackendBetaV1(
return inputObject return inputObject
} }
export function updateIngressBackend(
inputObject: any,
serviceNameMap: Map<string, string>
): any {
inputObject = JSON.parse(JSON.stringify(inputObject), (key, value) => {
if (
key.toLowerCase() === BACKEND &&
serviceNameMap.has(value?.service?.name)
) {
value.service.name = serviceNameMap.get(value.service.name)
}
return value
})
return inputObject
}
@@ -19,21 +19,21 @@ export async function deployBlueGreenService(
const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths) const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths)
// create deployments with green label value // create deployments with green label value
const workloadDeployment = await createWorkloadsWithLabel( const result = await createWorkloadsWithLabel(
kubectl, kubectl,
manifestObjects.deploymentEntityList, manifestObjects.deploymentEntityList,
GREEN_LABEL_VALUE GREEN_LABEL_VALUE
) )
// create other non deployment and non service entities
const newObjectsList = manifestObjects.otherObjects const newObjectsList = manifestObjects.otherObjects
.concat(manifestObjects.ingressEntityList) .concat(manifestObjects.ingressEntityList)
.concat(manifestObjects.unroutedServiceEntityList) .concat(manifestObjects.unroutedServiceEntityList)
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList) const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList)
if (manifestFiles.length > 0) await kubectl.apply(manifestFiles) if (manifestFiles.length > 0) await kubectl.apply(manifestFiles)
// returning deployment details to check for rollout stability // returning deployment details to check for rollout stability
return {workloadDeployment, newObjectsList} return result
} }
export async function promoteBlueGreenService( export async function promoteBlueGreenService(
@@ -76,6 +76,7 @@ export async function rejectBlueGreenService(
manifestObjects.deploymentEntityList manifestObjects.deploymentEntityList
) )
} }
export async function routeBlueGreenService( export async function routeBlueGreenService(
kubectl: Kubectl, kubectl: Kubectl,
nextLabel: string, nextLabel: string,
@@ -23,8 +23,7 @@ const MAX_VAL = 100
export async function deployBlueGreenSMI( export async function deployBlueGreenSMI(
kubectl: Kubectl, kubectl: Kubectl,
filePaths: string[], filePaths: string[]
annotations: {[key: string]: string} = {}
) { ) {
// get all kubernetes objects defined in manifest files // get all kubernetes objects defined in manifest files
const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths) const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths)
@@ -38,16 +37,14 @@ export async function deployBlueGreenSMI(
await kubectl.apply(manifestFiles) await kubectl.apply(manifestFiles)
// make extraservices and trafficsplit // make extraservices and trafficsplit
await setupSMI(kubectl, manifestObjects.serviceEntityList, annotations) await setupSMI(kubectl, manifestObjects.serviceEntityList)
// create new deloyments // create new deloyments
const workloadDeployment = await createWorkloadsWithLabel( return await createWorkloadsWithLabel(
kubectl, kubectl,
manifestObjects.deploymentEntityList, manifestObjects.deploymentEntityList,
GREEN_LABEL_VALUE GREEN_LABEL_VALUE
) )
return {workloadDeployment, newObjectsList}
} }
export async function promoteBlueGreenSMI(kubectl: Kubectl, manifestObjects) { export async function promoteBlueGreenSMI(kubectl: Kubectl, manifestObjects) {
@@ -71,18 +68,16 @@ export async function promoteBlueGreenSMI(kubectl: Kubectl, manifestObjects) {
export async function rejectBlueGreenSMI( export async function rejectBlueGreenSMI(
kubectl: Kubectl, kubectl: Kubectl,
filePaths: string[], filePaths: string[]
annotations: {[key: string]: string} = {}
) { ) {
// get all kubernetes objects defined in manifest files // get all kubernetes objects defined in manifest files
const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths) const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths)
// route trafficsplit to stable deployments // route trafficsplit to stable deploymetns
await routeBlueGreenSMI( await routeBlueGreenSMI(
kubectl, kubectl,
NONE_LABEL_VALUE, NONE_LABEL_VALUE,
manifestObjects.serviceEntityList, manifestObjects.serviceEntityList
annotations
) )
// delete rejected new bluegreen deployments // delete rejected new bluegreen deployments
@@ -96,11 +91,7 @@ export async function rejectBlueGreenSMI(
await cleanupSMI(kubectl, manifestObjects.serviceEntityList) await cleanupSMI(kubectl, manifestObjects.serviceEntityList)
} }
export async function setupSMI( export async function setupSMI(kubectl: Kubectl, serviceEntityList: any[]) {
kubectl: Kubectl,
serviceEntityList: any[],
annotations: {[key: string]: string} = {}
) {
const newObjectsList = [] const newObjectsList = []
const trafficObjectList = [] const trafficObjectList = []
@@ -126,8 +117,7 @@ export async function setupSMI(
createTrafficSplitObject( createTrafficSplitObject(
kubectl, kubectl,
inputObject.metadata.name, inputObject.metadata.name,
NONE_LABEL_VALUE, NONE_LABEL_VALUE
annotations
) )
}) })
} }
@@ -137,8 +127,7 @@ let trafficSplitAPIVersion = ''
async function createTrafficSplitObject( async function createTrafficSplitObject(
kubectl: Kubectl, kubectl: Kubectl,
name: string, name: string,
nextLabel: string, nextLabel: string
annotations: {[key: string]: string} = {}
): Promise<any> { ): Promise<any> {
// cache traffic split api version // cache traffic split api version
if (!trafficSplitAPIVersion) if (!trafficSplitAPIVersion)
@@ -156,8 +145,7 @@ async function createTrafficSplitObject(
apiVersion: trafficSplitAPIVersion, apiVersion: trafficSplitAPIVersion,
kind: 'TrafficSplit', kind: 'TrafficSplit',
metadata: { metadata: {
name: getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX), name: getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX)
annotations: annotations
}, },
spec: { spec: {
service: name, service: name,
@@ -206,16 +194,14 @@ export function getSMIServiceResource(
export async function routeBlueGreenSMI( export async function routeBlueGreenSMI(
kubectl: Kubectl, kubectl: Kubectl,
nextLabel: string, nextLabel: string,
serviceEntityList: any[], serviceEntityList: any[]
annotations: {[key: string]: string} = {}
) { ) {
for (const serviceObject of serviceEntityList) { for (const serviceObject of serviceEntityList) {
// route trafficsplit to given label // route trafficsplit to given label
await createTrafficSplitObject( await createTrafficSplitObject(
kubectl, kubectl,
serviceObject.metadata.name, serviceObject.metadata.name,
nextLabel, nextLabel
annotations
) )
} }
} }
@@ -288,8 +288,7 @@ async function getTrafficSplitObject(
name: string, name: string,
stableWeight: number, stableWeight: number,
baselineWeight: number, baselineWeight: number,
canaryWeight: number, canaryWeight: number
annotations: {[key: string]: string} = {}
): Promise<string> { ): Promise<string> {
// cached version // cached version
if (!trafficSplitAPIVersion) { if (!trafficSplitAPIVersion) {
@@ -302,8 +301,7 @@ async function getTrafficSplitObject(
apiVersion: trafficSplitAPIVersion, apiVersion: trafficSplitAPIVersion,
kind: 'TrafficSplit', kind: 'TrafficSplit',
metadata: { metadata: {
name: getTrafficSplitResourceName(name), name: getTrafficSplitResourceName(name)
annotations: annotations
}, },
spec: { spec: {
backends: [ backends: [
+6 -7
View File
@@ -41,8 +41,7 @@ export async function deployManifests(
files: string[], files: string[],
deploymentStrategy: DeploymentStrategy, deploymentStrategy: DeploymentStrategy,
kubectl: Kubectl, kubectl: Kubectl,
trafficSplitMethod: TrafficSplitMethod, trafficSplitMethod: TrafficSplitMethod
annotations: {[key: string]: string} = {}
): Promise<string[]> { ): Promise<string[]> {
switch (deploymentStrategy) { switch (deploymentStrategy) {
case DeploymentStrategy.CANARY: { case DeploymentStrategy.CANARY: {
@@ -60,16 +59,16 @@ export async function deployManifests(
core.getInput('route-method', {required: true}) core.getInput('route-method', {required: true})
) )
const {workloadDeployment, newObjectsList} = await Promise.resolve( const {result, newFilePaths} = await Promise.resolve(
(routeStrategy == RouteStrategy.INGRESS && (routeStrategy == RouteStrategy.INGRESS &&
deployBlueGreenIngress(kubectl, files)) || deployBlueGreenIngress(kubectl, files)) ||
(routeStrategy == RouteStrategy.SMI && (routeStrategy == RouteStrategy.SMI &&
deployBlueGreenSMI(kubectl, files, annotations)) || deployBlueGreenSMI(kubectl, files)) ||
deployBlueGreenService(kubectl, files) deployBlueGreenService(kubectl, files)
) )
checkForErrors([workloadDeployment.result]) checkForErrors([result])
return workloadDeployment.newFilePaths return newFilePaths
} }
case DeploymentStrategy.BASIC: { case DeploymentStrategy.BASIC: {
@@ -143,7 +142,7 @@ export async function annotateAndLabelResources(
const workflowFilePath = await getWorkflowFilePath(githubToken) const workflowFilePath = await getWorkflowFilePath(githubToken)
const deploymentConfig = await getDeploymentConfig() const deploymentConfig = await getDeploymentConfig()
const annotationKeyLabel = getWorkflowAnnotationKeyLabel() const annotationKeyLabel = getWorkflowAnnotationKeyLabel(workflowFilePath)
await annotateResources( await annotateResources(
files, files,
-8
View File
@@ -1,8 +0,0 @@
export function parseAnnotations(str: string) {
if (str == '') {
return {}
} else {
const annotaion = JSON.parse(str)
return new Map(annotaion)
}
}
+5 -12
View File
@@ -10,25 +10,18 @@ export interface Resource {
} }
export class Kubectl { export class Kubectl {
protected readonly kubectlPath: string private readonly kubectlPath: string
protected readonly namespace: string private readonly namespace: string
protected readonly ignoreSSLErrors: boolean private readonly ignoreSSLErrors: boolean
protected readonly resourceGroup: string
protected readonly name: string
protected isPrivateCluster: boolean
constructor( constructor(
kubectlPath: string, kubectlPath: string,
namespace: string = 'default', namespace: string = 'default',
ignoreSSLErrors: boolean = false, ignoreSSLErrors: boolean = false
resourceGroup: string = '',
name: string = ''
) { ) {
this.kubectlPath = kubectlPath this.kubectlPath = kubectlPath
this.ignoreSSLErrors = !!ignoreSSLErrors this.ignoreSSLErrors = !!ignoreSSLErrors
this.namespace = namespace this.namespace = namespace
this.resourceGroup = resourceGroup
this.name = name
} }
public async apply( public async apply(
@@ -162,7 +155,7 @@ export class Kubectl {
return this.execute(['delete', ...args]) return this.execute(['delete', ...args])
} }
protected async execute(args: string[], silent: boolean = false) { private async execute(args: string[], silent: boolean = false) {
if (this.ignoreSSLErrors) { if (this.ignoreSSLErrors) {
args.push('--insecure-skip-tls-verify') args.push('--insecure-skip-tls-verify')
} }
-135
View File
@@ -1,135 +0,0 @@
import {Kubectl} from './kubectl'
import {ExecOptions, ExecOutput, getExecOutput} from '@actions/exec'
import * as core from '@actions/core'
import * as os from 'os'
import * as fs from 'fs'
import * as path from 'path'
export class PrivateKubectl extends Kubectl {
protected async execute(args: string[], silent: boolean = false) {
args.unshift('kubectl')
let kubectlCmd = args.join(' ')
let addFileFlag = false
let eo = <ExecOptions>{silent}
if (this.containsFilenames(kubectlCmd)) {
// For private clusters, files will referenced solely by their basename
kubectlCmd = this.replaceFilnamesWithBasenames(kubectlCmd)
addFileFlag = true
}
const privateClusterArgs = [
'aks',
'command',
'invoke',
'--resource-group',
this.resourceGroup,
'--name',
this.name,
'--command',
kubectlCmd
]
if (addFileFlag) {
const filenames = this.extractFilesnames(kubectlCmd).split(' ')
const tempDirectory =
process.env['runner.tempDirectory'] || os.tmpdir() + '/manifests'
eo.cwd = tempDirectory
privateClusterArgs.push(...['--file', '.'])
let filenamesArr = filenames[0].split(',')
for (let index = 0; index < filenamesArr.length; index++) {
const file = filenamesArr[index]
if (!file) {
continue
}
this.moveFileToTempManifestDir(file)
}
}
core.debug(
`private cluster Kubectl run with invoke command: ${kubectlCmd}`
)
return await getExecOutput('az', privateClusterArgs, eo)
}
private replaceFilnamesWithBasenames(kubectlCmd: string) {
let exFilenames = this.extractFilesnames(kubectlCmd)
let filenames = exFilenames.split(' ')
let filenamesArr = filenames[0].split(',')
for (let index = 0; index < filenamesArr.length; index++) {
filenamesArr[index] = path.basename(filenamesArr[index])
}
let baseFilenames = filenamesArr.join()
let result = kubectlCmd.replace(exFilenames, baseFilenames)
return result
}
public extractFilesnames(strToParse: string) {
let start = strToParse.indexOf('-filename')
let offset = 7
if (start == -1) {
start = strToParse.indexOf('-f')
if (start == -1) {
return ''
}
offset = 0
}
let temp = strToParse.substring(start + offset)
let end = temp.indexOf(' -')
//End could be case where the -f flag was last, or -f is followed by some additonal flag and it's arguments
return temp.substring(3, end == -1 ? temp.length : end).trim()
}
private containsFilenames(str: string) {
return str.includes('-f ') || str.includes('filename ')
}
private createTempManifestsDirectory() {
const manifestsDir = '/tmp/manifests'
if (!fs.existsSync('/tmp/manifests')) {
fs.mkdirSync('/tmp/manifests', {recursive: true})
}
}
private moveFileToTempManifestDir(file: string) {
this.createTempManifestsDirectory()
if (!fs.existsSync('/tmp/' + file)) {
core.debug(
'/tmp/' +
file +
' does not exist, and therefore cannot be moved to the manifest directory'
)
}
fs.copyFile('/tmp/' + file, '/tmp/manifests/' + file, function (err) {
if (err) {
core.debug(
'Could not rename ' +
'/tmp/' +
file +
' to ' +
'/tmp/manifests/' +
file +
' ERROR: ' +
err
)
return
}
core.debug(
"Successfully moved file '" +
file +
"' from /tmp to /tmp/manifest directory"
)
})
}
}
+2 -3
View File
@@ -12,12 +12,11 @@ describe('File utils', () => {
'test/unit/manifests/manifest_test_dir/another_layer/deep-service.yaml', 'test/unit/manifests/manifest_test_dir/another_layer/deep-service.yaml',
'test/unit/manifests/manifest_test_dir/nested-test-service.yaml', 'test/unit/manifests/manifest_test_dir/nested-test-service.yaml',
'test/unit/manifests/test-ingress.yml', 'test/unit/manifests/test-ingress.yml',
'test/unit/manifests/test-ingress-new.yml',
'test/unit/manifests/test-service.yml' 'test/unit/manifests/test-service.yml'
] ]
// is there a more efficient way to test equality w random order? // is there a more efficient way to test equality w random order?
expect(testSearch).toHaveLength(6) expect(testSearch).toHaveLength(5)
expectedManifests.forEach((fileName) => { expectedManifests.forEach((fileName) => {
expect(testSearch).toContain(fileName) expect(testSearch).toContain(fileName)
}) })
@@ -54,7 +53,7 @@ describe('File utils', () => {
expect( expect(
getFilesFromDirectories([outerPath, fileAtOuter, innerPath]) getFilesFromDirectories([outerPath, fileAtOuter, innerPath])
).toHaveLength(6) ).toHaveLength(5)
}) })
}) })
+1 -1
View File
@@ -57,7 +57,7 @@ export function writeManifestToFile(
} }
function getManifestFileName(kind: string, name: string) { function getManifestFileName(kind: string, name: string) {
const filePath = `${kind}_${name}_${getCurrentTime().toString()}` const filePath = `${kind}_${name}_ ${getCurrentTime().toString()}`
const tempDirectory = getTempDirectory() const tempDirectory = getTempDirectory()
return path.join(tempDirectory, path.basename(filePath)) return path.join(tempDirectory, path.basename(filePath))
} }
+19 -1
View File
@@ -1,6 +1,24 @@
import {cleanLabel} from '../utilities/workflowAnnotationUtils' import {
cleanLabel,
prefixObjectKeys
} from '../utilities/workflowAnnotationUtils'
describe('WorkflowAnnotationUtils', () => { describe('WorkflowAnnotationUtils', () => {
describe('prefixObjectKeys', () => {
it('should prefix an object with a given prefix', () => {
const obj = {
foo: 'bar',
baz: 'qux'
}
const prefix = 'prefix.'
const expected = {
'prefix.foo': 'bar',
'prefix.baz': 'qux'
}
expect(prefixObjectKeys(obj, prefix)).toEqual(expected)
})
})
describe('cleanLabel', () => { describe('cleanLabel', () => {
it('should clean label', () => { it('should clean label', () => {
const alreadyClean = 'alreadyClean' const alreadyClean = 'alreadyClean'
+21 -4
View File
@@ -1,6 +1,13 @@
import {DeploymentConfig} from '../types/deploymentConfig' import {DeploymentConfig} from '../types/deploymentConfig'
const ANNOTATION_PREFIX = 'actions.github.com' const ANNOTATION_PREFIX = 'actions.github.com/'
export function prefixObjectKeys(obj: any, prefix: string): any {
return Object.keys(obj).reduce((newObj, key) => {
newObj[prefix + key] = obj[key]
return newObj
}, {})
}
export function getWorkflowAnnotations( export function getWorkflowAnnotations(
lastSuccessRunSha: string, lastSuccessRunSha: string,
@@ -24,11 +31,21 @@ export function getWorkflowAnnotations(
helmChartPaths: deploymentConfig.helmChartFilePaths, helmChartPaths: deploymentConfig.helmChartFilePaths,
provider: 'GitHub' provider: 'GitHub'
} }
return JSON.stringify(annotationObject) const prefixedAnnotationObject = prefixObjectKeys(
annotationObject,
ANNOTATION_PREFIX
)
return JSON.stringify(prefixedAnnotationObject)
} }
export function getWorkflowAnnotationKeyLabel(): string { export function getWorkflowAnnotationKeyLabel(
return `${ANNOTATION_PREFIX}/k8s-deploy` workflowFilePath: string
): string {
const hashKey = require('crypto')
.createHash('MD5')
.update(`${process.env.GITHUB_REPOSITORY}/${workflowFilePath}`)
.digest('hex')
return `githubWorkflow_${hashKey}`
} }
/** /**
-50
View File
@@ -1,50 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
backend:
service:
name: nginx-service
port:
number: 80