mirror of
https://github.com/Azure/k8s-deploy.git
synced 2026-06-21 18:59:27 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f742976337 |
@@ -74,9 +74,6 @@ Following are the key capabilities of this action:
|
||||
<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>
|
||||
</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>
|
||||
<td>percentage </br></br>(Optional but required if strategy is canary)</td>
|
||||
<td>Used to compute the number of replicas of '-baseline' and '-canary' variants of the workloads found in manifest files. For the specified percentage input, if (percentage * numberOfDesirerdReplicas)/100 is not a round number, the floor of this number is used while creating '-baseline' and '-canary'.<br/><br/>For example, if Deployment hello-world was found in the input manifest file with 'replicas: 4' and if 'strategy: canary' and 'percentage: 25' are given as inputs to the action, then the Deployments hello-world-baseline and hello-world-canary are created with 1 replica each. The '-baseline' variant is created with the same image and tag as the stable version (4 replica variant prior to deployment) while the '-canary' variant is created with the image and tag corresponding to the new changes being deployed</td>
|
||||
|
||||
@@ -35,9 +35,6 @@ inputs:
|
||||
description: 'Traffic split method to be used. Allowed values are pod and smi'
|
||||
required: false
|
||||
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:
|
||||
description: 'Baseline and canary replicas count. Valid value between 0 to 100 (inclusive)'
|
||||
required: false
|
||||
|
||||
@@ -19,8 +19,7 @@ import {parseRouteStrategy} from '../types/routeStrategy'
|
||||
export async function deploy(
|
||||
kubectl: Kubectl,
|
||||
manifestFilePaths: string[],
|
||||
deploymentStrategy: DeploymentStrategy,
|
||||
annotations: {[key: string]: string} = {}
|
||||
deploymentStrategy: DeploymentStrategy
|
||||
) {
|
||||
// update manifests
|
||||
const inputManifestFiles: string[] = updateManifestFiles(manifestFilePaths)
|
||||
@@ -35,8 +34,7 @@ export async function deploy(
|
||||
inputManifestFiles,
|
||||
deploymentStrategy,
|
||||
kubectl,
|
||||
trafficSplitMethod,
|
||||
annotations
|
||||
trafficSplitMethod
|
||||
)
|
||||
core.endGroup()
|
||||
core.debug('Deployed manifest files: ' + deployedManifestFiles)
|
||||
|
||||
+12
-26
@@ -40,15 +40,14 @@ import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy'
|
||||
export async function promote(
|
||||
kubectl: Kubectl,
|
||||
manifests: string[],
|
||||
deploymentStrategy: DeploymentStrategy,
|
||||
annotations: {[key: string]: string} = {}
|
||||
deploymentStrategy: DeploymentStrategy
|
||||
) {
|
||||
switch (deploymentStrategy) {
|
||||
case DeploymentStrategy.CANARY:
|
||||
await promoteCanary(kubectl, manifests)
|
||||
break
|
||||
case DeploymentStrategy.BLUE_GREEN:
|
||||
await promoteBlueGreen(kubectl, manifests, annotations)
|
||||
await promoteBlueGreen(kubectl, manifests)
|
||||
break
|
||||
default:
|
||||
throw Error('Invalid promote deployment strategy')
|
||||
@@ -66,30 +65,26 @@ async function promoteCanary(kubectl: Kubectl, manifests: string[]) {
|
||||
|
||||
// In case of SMI traffic split strategy when deployment is promoted, first we will redirect traffic to
|
||||
// canary deployment, then update stable deployment and then redirect traffic to stable deployment
|
||||
core.startGroup('Redirecting traffic to canary deployment')
|
||||
core.info('Redirecting traffic to canary deployment')
|
||||
await SMICanaryDeploymentHelper.redirectTrafficToCanaryDeployment(
|
||||
kubectl,
|
||||
manifests
|
||||
)
|
||||
core.endGroup()
|
||||
|
||||
core.startGroup('Deploying input manifests with SMI canary strategy')
|
||||
core.info('Deploying input manifests with SMI canary strategy')
|
||||
await deploy.deploy(kubectl, manifests, DeploymentStrategy.CANARY)
|
||||
core.endGroup()
|
||||
|
||||
core.startGroup('Redirecting traffic to stable deployment')
|
||||
core.info('Redirecting traffic to stable deployment')
|
||||
await SMICanaryDeploymentHelper.redirectTrafficToStableDeployment(
|
||||
kubectl,
|
||||
manifests
|
||||
)
|
||||
core.endGroup()
|
||||
} else {
|
||||
core.startGroup('Deploying input manifests')
|
||||
core.info('Deploying input manifests')
|
||||
await deploy.deploy(kubectl, manifests, DeploymentStrategy.CANARY)
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
core.startGroup('Deleting canary and baseline workloads')
|
||||
core.info('Deleting canary and baseline workloads')
|
||||
try {
|
||||
await canaryDeploymentHelper.deleteCanaryDeployment(
|
||||
kubectl,
|
||||
@@ -102,14 +97,9 @@ async function promoteCanary(kubectl: Kubectl, manifests: string[]) {
|
||||
ex
|
||||
)
|
||||
}
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
async function promoteBlueGreen(
|
||||
kubectl: Kubectl,
|
||||
manifests: string[],
|
||||
annotations: {[key: string]: string} = {}
|
||||
) {
|
||||
async function promoteBlueGreen(kubectl: Kubectl, manifests: string[]) {
|
||||
// update container images and pull secrets
|
||||
const inputManifestFiles: string[] = updateManifestFiles(manifests)
|
||||
const manifestObjects: BlueGreenManifests =
|
||||
@@ -119,7 +109,7 @@ async function promoteBlueGreen(
|
||||
core.getInput('route-method', {required: true})
|
||||
)
|
||||
|
||||
core.startGroup('Deleting old deployment and making new one')
|
||||
core.info('Deleting old deployment and making new one')
|
||||
let result
|
||||
if (routeStrategy == RouteStrategy.INGRESS) {
|
||||
result = await promoteBlueGreenIngress(kubectl, manifestObjects)
|
||||
@@ -128,10 +118,9 @@ async function promoteBlueGreen(
|
||||
} else {
|
||||
result = await promoteBlueGreenService(kubectl, manifestObjects)
|
||||
}
|
||||
core.endGroup()
|
||||
|
||||
// checking stability of newly created deployments
|
||||
core.startGroup('Checking manifest stability')
|
||||
core.info('Checking manifest stability')
|
||||
const deployedManifestFiles = result.newFilePaths
|
||||
const resources: Resource[] = getResources(
|
||||
deployedManifestFiles,
|
||||
@@ -140,9 +129,8 @@ async function promoteBlueGreen(
|
||||
])
|
||||
)
|
||||
await KubernetesManifestUtility.checkManifestStability(kubectl, resources)
|
||||
core.endGroup()
|
||||
|
||||
core.startGroup(
|
||||
core.info(
|
||||
'Routing to new deployments and deleting old workloads and services'
|
||||
)
|
||||
if (routeStrategy == RouteStrategy.INGRESS) {
|
||||
@@ -162,8 +150,7 @@ async function promoteBlueGreen(
|
||||
await routeBlueGreenSMI(
|
||||
kubectl,
|
||||
NONE_LABEL_VALUE,
|
||||
manifestObjects.serviceEntityList,
|
||||
annotations
|
||||
manifestObjects.serviceEntityList
|
||||
)
|
||||
await deleteWorkloadsWithLabel(
|
||||
kubectl,
|
||||
@@ -183,5 +170,4 @@ async function promoteBlueGreen(
|
||||
manifestObjects.deploymentEntityList
|
||||
)
|
||||
}
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
+7
-15
@@ -15,15 +15,14 @@ import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy'
|
||||
export async function reject(
|
||||
kubectl: Kubectl,
|
||||
manifests: string[],
|
||||
deploymentStrategy: DeploymentStrategy,
|
||||
annotations: {[key: string]: string} = {}
|
||||
deploymentStrategy: DeploymentStrategy
|
||||
) {
|
||||
switch (deploymentStrategy) {
|
||||
case DeploymentStrategy.CANARY:
|
||||
await rejectCanary(kubectl, manifests)
|
||||
break
|
||||
case DeploymentStrategy.BLUE_GREEN:
|
||||
await rejectBlueGreen(kubectl, manifests, annotations)
|
||||
await rejectBlueGreen(kubectl, manifests)
|
||||
break
|
||||
default:
|
||||
throw 'Invalid delete deployment strategy'
|
||||
@@ -37,30 +36,24 @@ async function rejectCanary(kubectl: Kubectl, manifests: string[]) {
|
||||
core.getInput('traffic-split-method', {required: true})
|
||||
)
|
||||
if (trafficSplitMethod == TrafficSplitMethod.SMI) {
|
||||
core.startGroup('Rejecting deployment with SMI canary strategy')
|
||||
core.info('Rejecting deployment with SMI canary strategy')
|
||||
includeServices = true
|
||||
await SMICanaryDeploymentHelper.redirectTrafficToStableDeployment(
|
||||
kubectl,
|
||||
manifests
|
||||
)
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
core.startGroup('Deleting baseline and canary workloads')
|
||||
core.info('Deleting baseline and canary workloads')
|
||||
await canaryDeploymentHelper.deleteCanaryDeployment(
|
||||
kubectl,
|
||||
manifests,
|
||||
includeServices
|
||||
)
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
async function rejectBlueGreen(
|
||||
kubectl: Kubectl,
|
||||
manifests: string[],
|
||||
annotations: {[key: string]: string} = {}
|
||||
) {
|
||||
core.startGroup('Rejecting deployment with blue green strategy')
|
||||
async function rejectBlueGreen(kubectl: Kubectl, manifests: string[]) {
|
||||
core.info('Rejecting deployment with blue green strategy')
|
||||
|
||||
const routeStrategy = parseRouteStrategy(
|
||||
core.getInput('route-method', {required: true})
|
||||
@@ -68,9 +61,8 @@ async function rejectBlueGreen(
|
||||
if (routeStrategy == RouteStrategy.INGRESS) {
|
||||
await rejectBlueGreenIngress(kubectl, manifests)
|
||||
} else if (routeStrategy == RouteStrategy.SMI) {
|
||||
await rejectBlueGreenSMI(kubectl, manifests, annotations)
|
||||
await rejectBlueGreenSMI(kubectl, manifests)
|
||||
} else {
|
||||
await rejectBlueGreenService(kubectl, manifests)
|
||||
}
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
+3
-7
@@ -6,7 +6,6 @@ import {reject} from './actions/reject'
|
||||
import {Action, parseAction} from './types/action'
|
||||
import {parseDeploymentStrategy} from './types/deploymentStrategy'
|
||||
import {getFilesFromDirectories} from './utilities/fileUtils'
|
||||
import {parseAnnotations} from './types/annotations'
|
||||
|
||||
export async function run() {
|
||||
// verify kubeconfig is set
|
||||
@@ -19,9 +18,6 @@ export async function run() {
|
||||
const action: Action | undefined = parseAction(
|
||||
core.getInput('action', {required: true})
|
||||
)
|
||||
const annotations = parseAnnotations(
|
||||
core.getInput('annotations', {required: false})
|
||||
)
|
||||
const strategy = parseDeploymentStrategy(core.getInput('strategy'))
|
||||
const manifestsInput = core.getInput('manifests', {required: true})
|
||||
const manifestFilePaths = manifestsInput
|
||||
@@ -38,15 +34,15 @@ export async function run() {
|
||||
// run action
|
||||
switch (action) {
|
||||
case Action.DEPLOY: {
|
||||
await deploy(kubectl, fullManifestFilePaths, strategy, annotations)
|
||||
await deploy(kubectl, fullManifestFilePaths, strategy)
|
||||
break
|
||||
}
|
||||
case Action.PROMOTE: {
|
||||
await promote(kubectl, fullManifestFilePaths, strategy, annotations)
|
||||
await promote(kubectl, fullManifestFilePaths, strategy)
|
||||
break
|
||||
}
|
||||
case Action.REJECT: {
|
||||
await reject(kubectl, fullManifestFilePaths, strategy, annotations)
|
||||
await reject(kubectl, fullManifestFilePaths, strategy)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -40,8 +40,7 @@ export interface BlueGreenManifests {
|
||||
export async function routeBlueGreen(
|
||||
kubectl: Kubectl,
|
||||
inputManifestFiles: string[],
|
||||
routeStrategy: RouteStrategy,
|
||||
annotations: {[key: string]: string} = {}
|
||||
routeStrategy: RouteStrategy
|
||||
) {
|
||||
// sleep for buffer time
|
||||
const bufferTime: number = parseInt(
|
||||
@@ -75,8 +74,7 @@ export async function routeBlueGreen(
|
||||
await routeBlueGreenSMI(
|
||||
kubectl,
|
||||
GREEN_LABEL_VALUE,
|
||||
manifestObjects.serviceEntityList,
|
||||
annotations
|
||||
manifestObjects.serviceEntityList
|
||||
)
|
||||
} else {
|
||||
await routeBlueGreenService(
|
||||
|
||||
@@ -23,8 +23,7 @@ const MAX_VAL = 100
|
||||
|
||||
export async function deployBlueGreenSMI(
|
||||
kubectl: Kubectl,
|
||||
filePaths: string[],
|
||||
annotations: {[key: string]: string} = {}
|
||||
filePaths: string[]
|
||||
) {
|
||||
// get all kubernetes objects defined in manifest files
|
||||
const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths)
|
||||
@@ -38,7 +37,7 @@ export async function deployBlueGreenSMI(
|
||||
await kubectl.apply(manifestFiles)
|
||||
|
||||
// make extraservices and trafficsplit
|
||||
await setupSMI(kubectl, manifestObjects.serviceEntityList, annotations)
|
||||
await setupSMI(kubectl, manifestObjects.serviceEntityList)
|
||||
|
||||
// create new deloyments
|
||||
return await createWorkloadsWithLabel(
|
||||
@@ -69,18 +68,16 @@ export async function promoteBlueGreenSMI(kubectl: Kubectl, manifestObjects) {
|
||||
|
||||
export async function rejectBlueGreenSMI(
|
||||
kubectl: Kubectl,
|
||||
filePaths: string[],
|
||||
annotations: {[key: string]: string} = {}
|
||||
filePaths: string[]
|
||||
) {
|
||||
// get all kubernetes objects defined in manifest files
|
||||
const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths)
|
||||
|
||||
// route trafficsplit to stable deployments
|
||||
// route trafficsplit to stable deploymetns
|
||||
await routeBlueGreenSMI(
|
||||
kubectl,
|
||||
NONE_LABEL_VALUE,
|
||||
manifestObjects.serviceEntityList,
|
||||
annotations
|
||||
manifestObjects.serviceEntityList
|
||||
)
|
||||
|
||||
// delete rejected new bluegreen deployments
|
||||
@@ -94,11 +91,7 @@ export async function rejectBlueGreenSMI(
|
||||
await cleanupSMI(kubectl, manifestObjects.serviceEntityList)
|
||||
}
|
||||
|
||||
export async function setupSMI(
|
||||
kubectl: Kubectl,
|
||||
serviceEntityList: any[],
|
||||
annotations: {[key: string]: string} = {}
|
||||
) {
|
||||
export async function setupSMI(kubectl: Kubectl, serviceEntityList: any[]) {
|
||||
const newObjectsList = []
|
||||
const trafficObjectList = []
|
||||
|
||||
@@ -124,8 +117,7 @@ export async function setupSMI(
|
||||
createTrafficSplitObject(
|
||||
kubectl,
|
||||
inputObject.metadata.name,
|
||||
NONE_LABEL_VALUE,
|
||||
annotations
|
||||
NONE_LABEL_VALUE
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -135,8 +127,7 @@ let trafficSplitAPIVersion = ''
|
||||
async function createTrafficSplitObject(
|
||||
kubectl: Kubectl,
|
||||
name: string,
|
||||
nextLabel: string,
|
||||
annotations: {[key: string]: string} = {}
|
||||
nextLabel: string
|
||||
): Promise<any> {
|
||||
// cache traffic split api version
|
||||
if (!trafficSplitAPIVersion)
|
||||
@@ -154,8 +145,7 @@ async function createTrafficSplitObject(
|
||||
apiVersion: trafficSplitAPIVersion,
|
||||
kind: 'TrafficSplit',
|
||||
metadata: {
|
||||
name: getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX),
|
||||
annotations: annotations
|
||||
name: getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX)
|
||||
},
|
||||
spec: {
|
||||
service: name,
|
||||
@@ -204,16 +194,14 @@ export function getSMIServiceResource(
|
||||
export async function routeBlueGreenSMI(
|
||||
kubectl: Kubectl,
|
||||
nextLabel: string,
|
||||
serviceEntityList: any[],
|
||||
annotations: {[key: string]: string} = {}
|
||||
serviceEntityList: any[]
|
||||
) {
|
||||
for (const serviceObject of serviceEntityList) {
|
||||
// route trafficsplit to given label
|
||||
await createTrafficSplitObject(
|
||||
kubectl,
|
||||
serviceObject.metadata.name,
|
||||
nextLabel,
|
||||
annotations
|
||||
nextLabel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,8 +288,7 @@ async function getTrafficSplitObject(
|
||||
name: string,
|
||||
stableWeight: number,
|
||||
baselineWeight: number,
|
||||
canaryWeight: number,
|
||||
annotations: {[key: string]: string} = {}
|
||||
canaryWeight: number
|
||||
): Promise<string> {
|
||||
// cached version
|
||||
if (!trafficSplitAPIVersion) {
|
||||
@@ -302,8 +301,7 @@ async function getTrafficSplitObject(
|
||||
apiVersion: trafficSplitAPIVersion,
|
||||
kind: 'TrafficSplit',
|
||||
metadata: {
|
||||
name: getTrafficSplitResourceName(name),
|
||||
annotations: annotations
|
||||
name: getTrafficSplitResourceName(name)
|
||||
},
|
||||
spec: {
|
||||
backends: [
|
||||
|
||||
@@ -23,8 +23,7 @@ import {parseRouteStrategy, RouteStrategy} from '../types/routeStrategy'
|
||||
import {ExecOutput} from '@actions/exec'
|
||||
import {
|
||||
getWorkflowAnnotationKeyLabel,
|
||||
getWorkflowAnnotations,
|
||||
cleanLabel
|
||||
getWorkflowAnnotations
|
||||
} from '../utilities/workflowAnnotationUtils'
|
||||
import {
|
||||
annotateChildPods,
|
||||
@@ -41,8 +40,7 @@ export async function deployManifests(
|
||||
files: string[],
|
||||
deploymentStrategy: DeploymentStrategy,
|
||||
kubectl: Kubectl,
|
||||
trafficSplitMethod: TrafficSplitMethod,
|
||||
annotations: {[key: string]: string} = {}
|
||||
trafficSplitMethod: TrafficSplitMethod
|
||||
): Promise<string[]> {
|
||||
switch (deploymentStrategy) {
|
||||
case DeploymentStrategy.CANARY: {
|
||||
@@ -64,7 +62,7 @@ export async function deployManifests(
|
||||
(routeStrategy == RouteStrategy.INGRESS &&
|
||||
deployBlueGreenIngress(kubectl, files)) ||
|
||||
(routeStrategy == RouteStrategy.SMI &&
|
||||
deployBlueGreenSMI(kubectl, files, annotations)) ||
|
||||
deployBlueGreenSMI(kubectl, files)) ||
|
||||
deployBlueGreenService(kubectl, files)
|
||||
)
|
||||
|
||||
@@ -216,10 +214,10 @@ async function labelResources(
|
||||
label: string
|
||||
) {
|
||||
const labels = [
|
||||
`workflowFriendlyName=${cleanLabel(
|
||||
normalizeWorkflowStrLabel(process.env.GITHUB_WORKFLOW)
|
||||
`workflowFriendlyName=${normalizeWorkflowStrLabel(
|
||||
process.env.GITHUB_WORKFLOW
|
||||
)}`,
|
||||
`workflow=${cleanLabel(label)}`
|
||||
`workflow=${label}`
|
||||
]
|
||||
|
||||
checkForErrors([await kubectl.labelFiles(files, labels)], true)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
export function parseAnnotations(str: string) {
|
||||
if (str == '') {
|
||||
return {}
|
||||
} else {
|
||||
const annotaion = JSON.parse(str)
|
||||
return new Map(annotaion)
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,7 @@ export async function getDeploymentConfig(): Promise<DeploymentConfig> {
|
||||
)
|
||||
}
|
||||
|
||||
let imageNames = core.getInput('images').split('\n') || []
|
||||
imageNames.forEach((element) => {
|
||||
if (element === null || '/^s+$/') {
|
||||
imageNames.pop()
|
||||
}
|
||||
})
|
||||
const imageNames = core.getInput('images').split('\n') || []
|
||||
const imageDockerfilePathMap: {[id: string]: string} = {}
|
||||
|
||||
const pullImages = !(core.getInput('pull-images').toLowerCase() === 'false')
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
cleanLabel,
|
||||
prefixObjectKeys
|
||||
} from '../utilities/workflowAnnotationUtils'
|
||||
import {prefixObjectKeys} from '../utilities/workflowAnnotationUtils'
|
||||
|
||||
describe('WorkflowAnnotationUtils', () => {
|
||||
describe('prefixObjectKeys', () => {
|
||||
@@ -18,16 +15,4 @@ describe('WorkflowAnnotationUtils', () => {
|
||||
expect(prefixObjectKeys(obj, prefix)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('cleanLabel', () => {
|
||||
it('should clean label', () => {
|
||||
const alreadyClean = 'alreadyClean'
|
||||
expect(cleanLabel(alreadyClean)).toEqual(alreadyClean)
|
||||
expect(cleanLabel('.startInvalid')).toEqual('startInvalid')
|
||||
expect(cleanLabel('with%S0ME&invalid#chars')).toEqual(
|
||||
'withS0MEinvalidchars'
|
||||
)
|
||||
expect(cleanLabel('with⚒️emoji')).toEqual('withemoji')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -47,14 +47,3 @@ export function getWorkflowAnnotationKeyLabel(
|
||||
.digest('hex')
|
||||
return `githubWorkflow_${hashKey}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans label to match valid kubernetes label specification by removing invalid characters
|
||||
* @param label
|
||||
* @returns cleaned label
|
||||
*/
|
||||
export function cleanLabel(label: string): string {
|
||||
const removedInvalidChars = label.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] || ''
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user