-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Deploy Tekton resources on save (#269)
* Deploy Tekton resources on save
- Loading branch information
1 parent
28d09fd
commit 651ed2d
Showing
6 changed files
with
315 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/*----------------------------------------------------------------------------------------------- | ||
* Copyright (c) Red Hat, Inc. All rights reserved. | ||
* Licensed under the MIT License. See LICENSE file in the project root for license information. | ||
*-----------------------------------------------------------------------------------------------*/ | ||
|
||
import * as os from 'os'; | ||
import * as path from 'path'; | ||
import { cli } from '../cli'; | ||
import * as fs from 'fs-extra'; | ||
import * as yaml from 'js-yaml'; | ||
import * as vscode from 'vscode'; | ||
import { contextGlobalState } from '../extension'; | ||
import { tektonYaml } from '../yaml-support/tkn-yaml'; | ||
import { pipelineExplorer } from '../pipeline/pipelineExplorer'; | ||
import { getStderrString, Command, newK8sCommand } from '../tkn'; | ||
|
||
|
||
function checkDeploy(): boolean { | ||
return vscode.workspace | ||
.getConfiguration('vs-tekton') | ||
.get<boolean>('deploy'); | ||
} | ||
|
||
export async function updateTektonResource(document: vscode.TextDocument): Promise<void> { | ||
let value: string; | ||
if (!checkDeploy()) return; | ||
if (document.languageId !== 'yaml') return; | ||
if (!(document.uri.scheme.startsWith('tekton'))) { | ||
const verifyTknYaml = tektonYaml.isTektonYaml(document); | ||
if (!contextGlobalState.workspaceState.get(document.uri.fsPath) && verifyTknYaml) { | ||
value = await vscode.window.showWarningMessage('Detected Tekton resources. Do you want to deploy to cluster?', 'Deploy', 'Deploy Once', 'Cancel'); | ||
} | ||
if (value === 'Deploy') { | ||
contextGlobalState.workspaceState.update(document.uri.fsPath, true); | ||
} | ||
if (verifyTknYaml && (/Deploy/.test(value) || contextGlobalState.workspaceState.get(document.uri.fsPath))) { | ||
const result = await cli.execute(Command.create(document.uri.fsPath)); | ||
if (result.error) { | ||
const tempPath = os.tmpdir(); | ||
if (!tempPath) { | ||
return; | ||
} | ||
const fsPath = path.join(tempPath, path.basename(document.uri.fsPath)); | ||
try { | ||
let yamlData = ''; | ||
const resourceCheckRegex = /^(Task|PipelineResource|Pipeline|Condition|ClusterTask|EventListener|TriggerBinding)$/ as RegExp; | ||
const fileContents = await fs.readFile(document.uri.fsPath, 'utf8'); | ||
const data: object[] = yaml.safeLoadAll(fileContents).filter((obj: {kind: string}) => resourceCheckRegex.test(obj.kind)); | ||
if (data.length === 0) return; | ||
data.map(value => { | ||
const yamlStr = yaml.safeDump(value); | ||
yamlData += yamlStr + '---\n'; | ||
}) | ||
await fs.writeFile(fsPath, yamlData, 'utf8'); | ||
} catch (err) { | ||
// ignore | ||
} | ||
const apply = await cli.execute(newK8sCommand(`apply -f ${fsPath}`)); | ||
await fs.unlink(fsPath); | ||
if (apply.error) { | ||
vscode.window.showErrorMessage(`Fail to deploy Resources: ${getStderrString(apply.error)}`); | ||
} else { | ||
pipelineExplorer.refresh(); | ||
vscode.window.showInformationMessage('Resources were successfully Deploy.'); | ||
} | ||
} else { | ||
pipelineExplorer.refresh(); | ||
vscode.window.showInformationMessage('Resources were successfully Created.'); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
/*----------------------------------------------------------------------------------------------- | ||
* Copyright (c) Red Hat, Inc. All rights reserved. | ||
* Licensed under the MIT License. See LICENSE file in the project root for license information. | ||
*-----------------------------------------------------------------------------------------------*/ | ||
|
||
'use strict'; | ||
|
||
import * as os from 'os'; | ||
import * as chai from 'chai'; | ||
import * as sinon from 'sinon'; | ||
import * as fs from 'fs-extra'; | ||
import * as vscode from 'vscode'; | ||
import { cli } from '../../src/cli'; | ||
import * as sinonChai from 'sinon-chai'; | ||
import { Command } from '../../src/tkn'; | ||
import { updateTektonResource } from '../../src/tekton/deploy'; | ||
import { contextGlobalState } from '../../src/extension'; | ||
import { tektonYaml } from '../../src/yaml-support/tkn-yaml'; | ||
import { pipelineExplorer } from '../../src/pipeline/pipelineExplorer'; | ||
|
||
const expect = chai.expect; | ||
chai.use(sinonChai); | ||
|
||
suite('Deploy File', () => { | ||
const sandbox = sinon.createSandbox(); | ||
let execStub: sinon.SinonStub; | ||
let osStub: sinon.SinonStub; | ||
let unlinkStub: sinon.SinonStub; | ||
let writeFileStub: sinon.SinonStub; | ||
let readFileStub: sinon.SinonStub; | ||
let showWarningMessageStub: sinon.SinonStub; | ||
let showErrorMessageStub: sinon.SinonStub; | ||
let workspaceStateGetStub: sinon.SinonStub; | ||
let workspaceStateUpdateStub: sinon.SinonStub; | ||
let showInformationMessageStub: sinon.SinonStub; | ||
|
||
const sampleYaml = ` | ||
# pipeline.yaml | ||
apiVersion: tekton.dev/v1beta1 | ||
kind: Pipeline | ||
metadata: | ||
name: sample-pipeline-cluster-task-4 | ||
spec: | ||
tasks: | ||
- name: cluster-task-pipeline-4 | ||
taskRef: | ||
name: cluster-task-pipeline-4 | ||
kind: ClusterTask | ||
`; | ||
|
||
const textDocument: vscode.TextDocument = { | ||
uri: { | ||
authority: '', | ||
fragment: '', | ||
fsPath: 'workspace.yaml', | ||
scheme: '', | ||
path: '', | ||
query: '', | ||
with: sandbox.stub(), | ||
toJSON: sandbox.stub() | ||
}, | ||
fileName: 'workspace.yaml', | ||
isClosed: false, | ||
isDirty: false, | ||
isUntitled: false, | ||
languageId: 'yaml', | ||
version: 1, | ||
eol: vscode.EndOfLine.CRLF, | ||
save: undefined, | ||
lineCount: 33, | ||
lineAt: undefined, | ||
getText: sinon.stub().returns(sampleYaml), | ||
getWordRangeAtPosition: undefined, | ||
offsetAt: undefined, | ||
positionAt: undefined, | ||
validatePosition: undefined, | ||
validateRange: undefined | ||
}; | ||
|
||
|
||
setup(() => { | ||
sandbox.stub(vscode.workspace, 'getConfiguration').returns({ | ||
get<T>(): Promise<T|undefined> { | ||
return Promise.resolve(undefined); | ||
}, | ||
update(): Promise<void> { | ||
return Promise.resolve(); | ||
}, | ||
inspect(): { | ||
key: string; | ||
} { | ||
return undefined; | ||
}, | ||
has(): boolean { | ||
return true; | ||
}, | ||
deploy: true | ||
}); | ||
osStub = sandbox.stub(os, 'tmpdir').resolves(); | ||
unlinkStub = sandbox.stub(fs, 'unlink').resolves(); | ||
writeFileStub = sandbox.stub(fs, 'writeFile').resolves(); | ||
readFileStub = sandbox.stub(fs, 'readFile').resolves(` | ||
apiVersion: tekton.dev/v1beta1 | ||
kind: Task | ||
metadata: | ||
name: print-data | ||
spec: | ||
workspaces: | ||
- name: storage | ||
readOnly: true | ||
params: | ||
- name: filename | ||
steps: | ||
- name: print-secrets | ||
image: ubuntu | ||
script: cat $(workspaces.storage.path)/$(params.filename) | ||
`); | ||
showWarningMessageStub = sandbox.stub(vscode.window, 'showWarningMessage').resolves(); | ||
showErrorMessageStub = sandbox.stub(vscode.window, 'showErrorMessage').resolves(); | ||
showInformationMessageStub = sandbox.stub(vscode.window, 'showInformationMessage').resolves(); | ||
workspaceStateGetStub = sandbox.stub(contextGlobalState.workspaceState, 'get').resolves(); | ||
workspaceStateUpdateStub = sandbox.stub(contextGlobalState.workspaceState, 'update').resolves(); | ||
execStub = sandbox.stub(cli, 'execute').resolves(); | ||
sandbox.stub(pipelineExplorer, 'refresh').resolves(); | ||
sandbox.stub(tektonYaml, 'isTektonYaml').resolves('ClusterTask'); | ||
}); | ||
|
||
teardown(() => { | ||
sandbox.restore(); | ||
}); | ||
|
||
suite('Deploy command', () => { | ||
|
||
test('calls the appropriate kubectl command to deploy on cluster', async () => { | ||
execStub.resolves({ | ||
error: undefined, | ||
stderr: '', | ||
stdout: 'successfully created' | ||
}); | ||
workspaceStateGetStub.onFirstCall().returns(undefined); | ||
showWarningMessageStub.onFirstCall().resolves('Deploy Once'); | ||
await updateTektonResource(textDocument); | ||
expect(execStub).calledOnceWith(Command.create('workspace.yaml')); | ||
unlinkStub.calledOnce; | ||
osStub.calledOnce; | ||
readFileStub.calledOnce; | ||
writeFileStub.calledOnce; | ||
showInformationMessageStub.calledOnce; | ||
showWarningMessageStub.calledOnce; | ||
workspaceStateGetStub.calledOnce; | ||
}); | ||
|
||
test('get deploy data from workspaceState', async () => { | ||
execStub.resolves({ | ||
error: undefined, | ||
stderr: '', | ||
stdout: 'successfully created' | ||
}); | ||
workspaceStateGetStub.onFirstCall().returns('path'); | ||
await updateTektonResource(textDocument); | ||
expect(execStub).calledOnceWith(Command.create('workspace.yaml')); | ||
showInformationMessageStub.calledOnce; | ||
showWarningMessageStub.calledOnce; | ||
workspaceStateGetStub.calledOnce; | ||
}); | ||
|
||
test('Update the yaml if fail to create resources', async () => { | ||
execStub.onFirstCall().resolves({ | ||
error: 'error', | ||
stderr: 'error', | ||
stdout: '' | ||
}); | ||
execStub.onSecondCall().resolves({ | ||
error: '', | ||
stderr: '', | ||
stdout: 'successfully Deploy' | ||
}); | ||
osStub.returns('path'); | ||
workspaceStateGetStub.onFirstCall().returns('path'); | ||
await updateTektonResource(textDocument); | ||
showErrorMessageStub.calledOnce; | ||
workspaceStateGetStub.calledOnce; | ||
showInformationMessageStub.calledOnce; | ||
}); | ||
|
||
test('Throw error when apply command fails', async () => { | ||
execStub.onFirstCall().resolves({ | ||
error: 'error', | ||
stderr: 'error', | ||
stdout: '' | ||
}); | ||
execStub.onSecondCall().resolves({ | ||
error: 'error', | ||
stderr: 'error', | ||
stdout: '' | ||
}); | ||
osStub.returns('path'); | ||
workspaceStateGetStub.onFirstCall().returns('path'); | ||
await updateTektonResource(textDocument); | ||
showErrorMessageStub.calledTwice; | ||
workspaceStateGetStub.calledOnce; | ||
}); | ||
|
||
test('update the path to workspaceState', async () => { | ||
execStub.resolves({ | ||
error: undefined, | ||
stderr: '', | ||
stdout: 'successfully created' | ||
}); | ||
workspaceStateGetStub.onFirstCall().returns(undefined); | ||
showWarningMessageStub.onFirstCall().resolves('Deploy'); | ||
workspaceStateUpdateStub.onFirstCall().resolves('path'); | ||
await updateTektonResource(textDocument); | ||
expect(execStub).calledOnceWith(Command.create('workspace.yaml')); | ||
showInformationMessageStub.calledOnce; | ||
showWarningMessageStub.calledOnce; | ||
workspaceStateGetStub.calledOnce; | ||
workspaceStateUpdateStub.calledOnce; | ||
}); | ||
|
||
}); | ||
}); |