Skip to content

Commit

Permalink
Private Cluster functionality (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcus-chris-hines authored Jul 28, 2022
1 parent bb0278d commit 0b57955
Show file tree
Hide file tree
Showing 8 changed files with 2,605 additions and 2,041 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ 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>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>
<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>
<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>
Expand All @@ -122,6 +126,26 @@ Following are the key capabilities of this action:
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
```yaml
Expand Down
10 changes: 10 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ inputs:
description: 'Annotate the target namespace'
required: false
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:
color: 'green'
Expand Down
4,438 changes: 2,407 additions & 2,031 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@
"@types/jest": "^26.0.0",
"@types/js-yaml": "^3.12.7",
"@types/node": "^12.20.41",
"@vercel/ncc": "^0.34.0",
"jest": "^26.0.0",
"prettier": "2.7.1",
"prettier": "^2.7.1",
"ts-jest": "^26.0.0",
"typescript": "3.9.5"
}
Expand Down
17 changes: 15 additions & 2 deletions src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {reject} from './actions/reject'
import {Action, parseAction} from './types/action'
import {parseDeploymentStrategy} from './types/deploymentStrategy'
import {getFilesFromDirectories} from './utilities/fileUtils'
import {PrivateKubectl} from './types/privatekubectl'
import {parseAnnotations} from './types/annotations'

export async function run() {
Expand All @@ -30,10 +31,22 @@ export async function run() {
.filter((manifest) => manifest.length > 0) // remove any blanks

const fullManifestFilePaths = getFilesFromDirectories(manifestFilePaths)
// create kubectl
const kubectlPath = await getKubectlPath()
const namespace = core.getInput('namespace') || 'default'
const kubectl = new Kubectl(kubectlPath, namespace, true)
const isPrivateCluster =
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
switch (action) {
Expand Down
17 changes: 12 additions & 5 deletions src/types/kubectl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,25 @@ export interface Resource {
}

export class Kubectl {
private readonly kubectlPath: string
private readonly namespace: string
private readonly ignoreSSLErrors: boolean
protected readonly kubectlPath: string
protected readonly namespace: string
protected readonly ignoreSSLErrors: boolean
protected readonly resourceGroup: string
protected readonly name: string
protected isPrivateCluster: boolean

constructor(
kubectlPath: string,
namespace: string = 'default',
ignoreSSLErrors: boolean = false
ignoreSSLErrors: boolean = false,
resourceGroup: string = '',
name: string = ''
) {
this.kubectlPath = kubectlPath
this.ignoreSSLErrors = !!ignoreSSLErrors
this.namespace = namespace
this.resourceGroup = resourceGroup
this.name = name
}

public async apply(
Expand Down Expand Up @@ -155,7 +162,7 @@ export class Kubectl {
return this.execute(['delete', ...args])
}

private async execute(args: string[], silent: boolean = false) {
protected async execute(args: string[], silent: boolean = false) {
if (this.ignoreSSLErrors) {
args.push('--insecure-skip-tls-verify')
}
Expand Down
135 changes: 135 additions & 0 deletions src/types/privatekubectl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
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 changes: 1 addition & 1 deletion src/utilities/fileUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function writeManifestToFile(
}

function getManifestFileName(kind: string, name: string) {
const filePath = `${kind}_${name}_ ${getCurrentTime().toString()}`
const filePath = `${kind}_${name}_${getCurrentTime().toString()}`
const tempDirectory = getTempDirectory()
return path.join(tempDirectory, path.basename(filePath))
}
Expand Down

0 comments on commit 0b57955

Please sign in to comment.