diff --git a/Tasks/Kubernetes/Strings/resources.resjson/en-US/resources.resjson b/Tasks/Kubernetes/Strings/resources.resjson/en-US/resources.resjson index 98ea879e471d..5fd52bdda213 100644 --- a/Tasks/Kubernetes/Strings/resources.resjson/en-US/resources.resjson +++ b/Tasks/Kubernetes/Strings/resources.resjson/en-US/resources.resjson @@ -5,6 +5,7 @@ "loc.instanceNameFormat": "kubectl $(command)", "loc.group.displayName.commands": "Commands", "loc.group.displayName.secrets": "Secrets", + "loc.group.displayName.configMaps": "ConfigMaps", "loc.group.displayName.advanced": "Advanced", "loc.group.displayName.output": "Output", "loc.input.label.kubernetesServiceEndpoint": "Kubernetes Service Connection", @@ -22,7 +23,7 @@ "loc.input.label.secretType": "Type of secret", "loc.input.help.secretType": "Select a type of secret", "loc.input.label.secretArguments": "Arguments", - "loc.input.help.secretArguments": "Specify keys and literal values to insert in secret.For example, --from-literal=key1=value1 --from-literal=key2=\"top secret\". Please use double quotes to specify any literals that have spaces.", + "loc.input.help.secretArguments": "Specify keys and literal values to insert in secret.For example, --from-literal=key1=value1 --from-literal=key2=\"top secret\".", "loc.input.label.containerRegistryType": "Container Registry type", "loc.input.help.containerRegistryType": "Select a Container registry type.", "loc.input.label.dockerRegistryEndpoint": "Docker Registry Connection", @@ -35,6 +36,12 @@ "loc.input.help.secretName": "Name of the secret. You can use this secret name in the Kubernetes YAML configuration file.", "loc.input.label.forceUpdate": "Force update secret", "loc.input.help.forceUpdate": "Delete the secret if it exists and create a new one with updated values.", + "loc.input.label.configMapName": "ConfigMap Name", + "loc.input.help.configMapName": "Name of ConfigMap.", + "loc.input.label.forceUpdateConfigMap": "Force update configmap", + "loc.input.help.forceUpdateConfigMap": "Delete the configmap if it exists and create a new one with updated values.", + "loc.input.label.configMapArguments": "Arguments", + "loc.input.help.configMapArguments": "Specify keys and literal values to insert in configMap.For example, --from-literal=key1=value1 --from-literal=key2=\"top secret\".", "loc.input.label.versionOrLocation": "Kubectl", "loc.input.label.versionSpec": "Version Spec", "loc.input.help.versionSpec": "Version Spec of version to get. Examples: 1.7.0, 1.x.0, 4.x.0, 6.10.0, >=6.10.0", @@ -51,6 +58,10 @@ "loc.messages.DownloadingClient": "Downloading kubernetes client.", "loc.messages.CreatingSecret": "Executing create %s secret.", "loc.messages.DeleteSecret": "Executing delete %s secret", + "loc.messages.CreatingConfigMap": "Executing create %s configmap.", + "loc.messages.DeleteConfigMap": "Executing delete %s configmap", + "loc.messages.ConfigMapExists": "ConfigMap %s already exists", + "loc.messages.GetConfigMap": "Executing get %s configmap", "loc.messages.DockerRegistryConnectionNotSpecified": "Docker Registry connection details not specified", "loc.messages.FileNotFoundException": "Can not find file at location: %s", "loc.messages.DownloadingKubeCtlFromUrl": "Downloading Kubectl from Url: %s", diff --git a/Tasks/Kubernetes/Tests/L0.ts b/Tasks/Kubernetes/Tests/L0.ts index fcc9123b340c..0b403731e5eb 100644 --- a/Tasks/Kubernetes/Tests/L0.ts +++ b/Tasks/Kubernetes/Tests/L0.ts @@ -25,6 +25,9 @@ describe('Kubernetes Suite', function() { delete process.env[shared.TestEnvVars.secretArguments]; delete process.env[shared.TestEnvVars.secretName]; delete process.env[shared.TestEnvVars.forceUpdate]; + delete process.env[shared.TestEnvVars.configMapName]; + delete process.env[shared.TestEnvVars.forceUpdateConfigMap]; + delete process.env[shared.TestEnvVars.configMapArguments]; delete process.env[shared.TestEnvVars.outputFormat]; delete process.env[shared.TestEnvVars.kubectlOutput]; }); @@ -311,6 +314,88 @@ describe('Kubernetes Suite', function() { console.log(tr.stderr); done(); }); + + /* it('Runs successfully for kubectl create configMap from file or directory with forceUpdate', (done:MochaDone) => { + let tp = path.join(__dirname, 'TestSetup.js'); + let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp); + process.env[shared.TestEnvVars.command] = shared.Commands.get; + process.env[shared.TestEnvVars.arguments] = "pods"; + process.env[shared.TestEnvVars.configMapName] = "myConfigMap"; + process.env[shared.TestEnvVars.useConfigMapFile] = "true"; + process.env[shared.TestEnvVars.forceUpdateConfigMap] = "true"; + tr.run(); + + assert(tr.invokedToolCount == 2, 'should have invoked tool one times. actual: ' + tr.invokedToolCount); + assert(tr.stderr.length == 0 || tr.errorIssues.length, 'should not have written to stderr'); + assert(tr.succeeded, 'task should have succeeded'); + assert(tr.stdout.indexOf(`GetConfigMap myConfigMap`) == -1, "kubectl get should not run"); + assert(tr.stdout.indexOf(`DeleteConfigMap myConfigMap`) != -1, "kubectl delete should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create configmap myConfigMap --from-file=${shared.formatPath("configMapDir/configMap.properties")}`) != -1, "kubectl create should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} get pods`) != -1, "kubectl get should run"); + console.log(tr.stderr); + done(); + }); + + it('Runs successfully for kubectl create configMap from file or directory without forceUpdate', (done:MochaDone) => { + let tp = path.join(__dirname, 'TestSetup.js'); + let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp); + process.env[shared.TestEnvVars.command] = shared.Commands.get; + process.env[shared.TestEnvVars.arguments] = "pods"; + process.env[shared.TestEnvVars.configMapName] = "myConfigMap"; + process.env[shared.TestEnvVars.useConfigMapFile] = "true"; + tr.run(); + + assert(tr.invokedToolCount == 2, 'should have invoked tool one times. actual: ' + tr.invokedToolCount); + assert(tr.stderr.length == 0 || tr.errorIssues.length, 'should not have written to stderr'); + assert(tr.succeeded, 'task should have succeeded'); + assert(tr.stdout.indexOf(`GetConfigMap myConfigMap`) != -1, "kubectl get should run"); + assert(tr.stdout.indexOf(`DeleteConfigMap myConfigMap`) == -1, "kubectl delete should not run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create configmap myConfigMap --from-file=${shared.formatPath("configMapDir/configMap.properties")}`) != -1, "kubectl create should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} get pods`) != -1, "kubectl get should run"); + console.log(tr.stderr); + done(); + }); */ + + it('Runs successfully for kubectl create configMap using literal values with forceUpdate', (done:MochaDone) => { + let tp = path.join(__dirname, 'TestSetup.js'); + let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp); + process.env[shared.TestEnvVars.command] = shared.Commands.get; + process.env[shared.TestEnvVars.arguments] = "pods"; + process.env[shared.TestEnvVars.configMapName] = "myConfigMap"; + process.env[shared.TestEnvVars.configMapArguments] = "--from-literal=key1=value1 --from-literal=key2=value2"; + process.env[shared.TestEnvVars.forceUpdateConfigMap] = "true"; + tr.run(); + + assert(tr.invokedToolCount == 2, 'should have invoked tool one times. actual: ' + tr.invokedToolCount); + assert(tr.stderr.length == 0 || tr.errorIssues.length, 'should not have written to stderr'); + assert(tr.succeeded, 'task should have succeeded'); + assert(tr.stdout.indexOf(`GetConfigMap myConfigMap`) == -1, "kubectl get should not run"); + assert(tr.stdout.indexOf(`DeleteConfigMap myConfigMap`) != -1, "kubectl delete should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create configmap myConfigMap --from-literal=key1=value1 --from-literal=key2=value2`) != -1, "kubectl create should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} get pods`) != -1, "kubectl get should run"); + console.log(tr.stderr); + done(); + }); + + it('Runs successfully for kubectl kubectl create configMap using literal values without forceUpdate', (done:MochaDone) => { + let tp = path.join(__dirname, 'TestSetup.js'); + let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp); + process.env[shared.TestEnvVars.command] = shared.Commands.get; + process.env[shared.TestEnvVars.arguments] = "pods"; + process.env[shared.TestEnvVars.configMapName] = "myConfigMap"; + process.env[shared.TestEnvVars.configMapArguments] = "--from-literal=key1=value1 --from-literal=key2=value2"; + tr.run(); + + assert(tr.invokedToolCount == 2, 'should have invoked tool one times. actual: ' + tr.invokedToolCount); + assert(tr.stderr.length == 0 || tr.errorIssues.length, 'should not have written to stderr'); + assert(tr.succeeded, 'task should have succeeded'); + assert(tr.stdout.indexOf(`GetConfigMap myConfigMap`) != -1, "kubectl get should run"); + assert(tr.stdout.indexOf(`DeleteConfigMap myConfigMap`) == -1, "kubectl delete should not run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create configmap myConfigMap --from-literal=key1=value1 --from-literal=key2=value2`) != -1, "kubectl create should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} get pods`) != -1, "kubectl get should run"); + console.log(tr.stderr); + done(); + }); it('Runs successfully for kubectl get and print the output in a particular format', (done:MochaDone) => { let tp = path.join(__dirname, 'TestSetup.js'); @@ -327,5 +412,76 @@ describe('Kubernetes Suite', function() { assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} get secrets my-secret -o yaml`) != -1, "kubectl get should run"); console.log(tr.stderr); done(); + }); + + + it('Runs successfully for checking whether secrets, configmaps and kubectl commands are run in a consecutive manner', (done:MochaDone) => { + let tp = path.join(__dirname, 'TestSetup.js'); + let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp); + process.env[shared.TestEnvVars.command] = shared.Commands.get; + process.env[shared.TestEnvVars.arguments] = "pods"; + process.env[shared.TestEnvVars.secretType] = "generic"; + process.env[shared.TestEnvVars.secretArguments] = "--from-literal=key1=value1 --from-literal=key2=value2"; + process.env[shared.TestEnvVars.secretName] = "my-secret"; + process.env[shared.TestEnvVars.configMapName] = "myConfigMap"; + process.env[shared.TestEnvVars.configMapArguments] = "--from-literal=key1=value1 --from-literal=key2=value2"; + process.env[shared.TestEnvVars.forceUpdateConfigMap] = "true"; + tr.run(); + + assert(tr.invokedToolCount == 3, 'should have invoked tool one times. actual: ' + tr.invokedToolCount); + assert(tr.stderr.length == 0 || tr.errorIssues.length, 'should not have written to stderr'); + assert(tr.succeeded, 'task should have succeeded'); + assert(tr.stdout.indexOf(`DeleteSecret my-secret`) != -1, "kubectl delete should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create secret generic my-secret --from-literal=key1=value1 --from-literal=key2=value2`) != -1, "kubectl create should run"); + assert(tr.stdout.indexOf(`DeleteConfigMap myConfigMap`) != -1, "kubectl delete should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create configmap myConfigMap --from-literal=key1=value1 --from-literal=key2=value2`) != -1, "kubectl create should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} get pods`) != -1, "kubectl get should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create secret generic my-secret --from-literal=key1=value1 --from-literal=key2=value2`) < tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create configmap myConfigMap --from-literal=key1=value1 --from-literal=key2=value2`), "kubectl create secrets should run before create configMap"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create configmap myConfigMap --from-literal=key1=value1 --from-literal=key2=value2`) < tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} get pods`), "kubectl create configMap should run before get"); + console.log(tr.stderr); + done(); + }); + + it('Runs fails if create config command fails even if create secret is successful', (done:MochaDone) => { + let tp = path.join(__dirname, 'TestSetup.js'); + let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp); + process.env[shared.TestEnvVars.command] = shared.Commands.get; + process.env[shared.TestEnvVars.arguments] = "pods"; + process.env[shared.TestEnvVars.secretType] = "generic"; + process.env[shared.TestEnvVars.secretArguments] = "--from-literal=key1=value1 --from-literal=key2=value2"; + process.env[shared.TestEnvVars.secretName] = "my-secret"; + process.env[shared.TestEnvVars.configMapName] = "someConfigMap"; + process.env[shared.TestEnvVars.configMapArguments] = "--from-literal=key1=value1 --from-literal=key2=value2"; + tr.run(); + + assert(tr.invokedToolCount == 2, 'should have invoked tool one times. actual: ' + tr.invokedToolCount); + assert(tr.stderr.length == 0 || tr.errorIssues.length, 'should not have written to stderr'); + assert(tr.failed, 'task should have failed'); + assert(tr.stdout.indexOf(`GetConfigMap someConfigMap`) != -1, "kubectl get should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create secret generic my-secret --from-literal=key1=value1 --from-literal=key2=value2`) != -1, "kubectl create should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create configmap someConfigMap --from-literal=key1=value1 --from-literal=key2=value2`) != -1, "kubectl create should run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} get pods`) == -1, "kubectl get should not run"); + console.log(tr.stderr); + done(); + }); + + it('Runs successfully when forceUpdateConfigMap is false and configMap exists', (done:MochaDone) => { + let tp = path.join(__dirname, 'TestSetup.js'); + let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp); + process.env[shared.TestEnvVars.command] = shared.Commands.get; + process.env[shared.TestEnvVars.arguments] = "pods"; + process.env[shared.TestEnvVars.configMapName] = "existingConfigMap"; + process.env[shared.TestEnvVars.configMapArguments] = "--from-literal=key1=value1 --from-literal=key2=value2"; + tr.run(); + + assert(tr.invokedToolCount == 1, 'should have invoked tool one times. actual: ' + tr.invokedToolCount); + assert(tr.stderr.length == 0 || tr.errorIssues.length, 'should not have written to stderr'); + assert(tr.succeeded, 'task should have succeeded'); + assert(tr.stdout.indexOf(`GetConfigMap existingConfigMap`) != -1, "kubectl get should run"); + assert(tr.stdout.indexOf(`DeleteConfigMap existingConfigMap`) == -1, "kubectl delete should not run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} create configmap existingConfigMap --from-literal=key1=value1 --from-literal=key2=value2`) == -1, "kubectl create should not run"); + assert(tr.stdout.indexOf(`[command]kubectl --kubeconfig ${shared.formatPath("newUserDir/config")} get pods`) != -1, "kubectl get should run"); + console.log(tr.stderr); + done(); }); }); \ No newline at end of file diff --git a/Tasks/Kubernetes/Tests/TestSetup.ts b/Tasks/Kubernetes/Tests/TestSetup.ts index 69c421e9cd62..1969a56fd0cb 100644 --- a/Tasks/Kubernetes/Tests/TestSetup.ts +++ b/Tasks/Kubernetes/Tests/TestSetup.ts @@ -8,6 +8,7 @@ const ConfigurationFilePath = shared.formatPath("dir/deployment.yaml"); const newUserDirPath = shared.formatPath("newUserDir/"); const KubconfigFile = shared.formatPath("newUserDir/config"); const KubectlPath = shared.formatPath("newUserDir/kubectl.exe"); +const ConfigMapFilePath = shared.formatPath("configMapDir/configMap.properties"); let taskPath = path.join(__dirname, '../src', 'kubernetes.js'); let tr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); @@ -22,6 +23,11 @@ tr.setInput('secretType', process.env[shared.TestEnvVars.secretType] || 'dockerR tr.setInput('secretArguments', process.env[shared.TestEnvVars.secretArguments] || ''); tr.setInput('secretName', process.env[shared.TestEnvVars.secretName] || ''); tr.setInput('forceUpdate', process.env[shared.TestEnvVars.forceUpdate] || "true"); +tr.setInput('configMapName', process.env[shared.TestEnvVars.configMapName] || ''); +tr.setInput('forceUpdateConfigMap', process.env[shared.TestEnvVars.forceUpdateConfigMap] || "false"); +//tr.setInput('useConfigMapFile', process.env[shared.TestEnvVars.useConfigMapFile] || "false"); +//tr.setInput('configMapFile', ConfigMapFilePath); +tr.setInput('configMapArguments', process.env[shared.TestEnvVars.configMapArguments] || ''); tr.setInput('versionOrLocation', process.env[shared.TestEnvVars.versionOrLocation] || 'version'); tr.setInput('versionSpec', process.env[shared.TestEnvVars.versionSpec] || "1.7.0"); tr.setInput('checkLatest', process.env[shared.TestEnvVars.checkLatest] || "false"); @@ -53,7 +59,8 @@ let a = { }, "checkPath": { [KubectlPath]: true, - [ConfigurationFilePath]: true + [ConfigurationFilePath]: true, + [ConfigMapFilePath]: true }, "exist": { [KubconfigFile]: true @@ -64,6 +71,7 @@ let a = { // Add extra answer definitions that need to be dynamically generated a.exist[ConfigurationFilePath] = true; +a.exist[ConfigMapFilePath] = true; a.exist[KubectlPath] = true; if (JSON.parse(process.env[shared.isKubectlPresentOnMachine])) @@ -103,6 +111,28 @@ a.exec[`kubectl --kubeconfig ${KubconfigFile} create secret docker-registry my-s a.exec[`kubectl --kubeconfig ${KubconfigFile} create secret generic my-secret --from-literal=key1=value1 --from-literal=key2=value2`] = { "code": 0 }; +a.exec[`kubectl --kubeconfig ${KubconfigFile} delete configmap myConfigMap`] = { + "code": 0 +}; +a.exec[`kubectl --kubeconfig ${KubconfigFile} get configmap existingConfigMap`] = { + "code": 0 +}; +a.exec[`kubectl --kubeconfig ${KubconfigFile} get configmap someConfigMap`] = { + "code": 1 +}; +a.exec[`kubectl --kubeconfig ${KubconfigFile} get configmap myConfigMap`] = { + "code": 1 +}; +a.exec[`kubectl --kubeconfig ${KubconfigFile} create configmap myConfigMap --from-file=${ConfigMapFilePath}`] = { + "code": 0 +}; +a.exec[`kubectl --kubeconfig ${KubconfigFile} create configmap myConfigMap --from-literal=key1=value1 --from-literal=key2=value2`] = { + "code": 0 +}; +a.exec[`kubectl --kubeconfig ${KubconfigFile} create configmap someConfigMap --from-literal=key1=value1 --from-literal=key2=value2`] = { + "code": 1, + "stdout" : "Error in configMap creation" +}; a.exec[`kubectl --kubeconfig ${KubconfigFile} get secrets my-secret -o yaml`] = { "code": 0, "stdout": "successfully got secret my-secret and printed it in the specified format" @@ -199,7 +229,7 @@ tr.registerMock('./utilities', { }, assertFileExists: function(path) { return true; - } + } }); tr.run(); \ No newline at end of file diff --git a/Tasks/Kubernetes/Tests/TestShared.ts b/Tasks/Kubernetes/Tests/TestShared.ts index fcc10c7c35b3..80878d711c43 100644 --- a/Tasks/Kubernetes/Tests/TestShared.ts +++ b/Tasks/Kubernetes/Tests/TestShared.ts @@ -12,6 +12,9 @@ export let TestEnvVars = { secretArguments: "__secretArguments__", secretName: "__secretName__", forceUpdate: "__forceUpdate__", + configMapName: "__configMapName__", + forceUpdateConfigMap: "__forceUpdateConfigMap__", + configMapArguments: "__configMapArguments__", versionOrLocation: "__versionOrLocation__", versionSpec: "__versionSpec__", checkLatest: "__checkLatest__", diff --git a/Tasks/Kubernetes/src/clusterconnection.ts b/Tasks/Kubernetes/src/clusterconnection.ts index 6524f2b3c614..1a8c68104f42 100644 --- a/Tasks/Kubernetes/src/clusterconnection.ts +++ b/Tasks/Kubernetes/src/clusterconnection.ts @@ -49,7 +49,7 @@ export default class ClusterConnection { // close kubernetes connection public close(): void { - // all configuration ase in agent temp directory. Hence automatically deleted. + // all configuration are in agent temp directory. Hence automatically deleted. } //excute kubernetes command diff --git a/Tasks/Kubernetes/src/kubernetes.ts b/Tasks/Kubernetes/src/kubernetes.ts index ac73eac7f582..cfe1736935f2 100644 --- a/Tasks/Kubernetes/src/kubernetes.ts +++ b/Tasks/Kubernetes/src/kubernetes.ts @@ -5,6 +5,7 @@ import path = require('path'); import ClusterConnection from "./clusterconnection"; import * as kubectl from "./kubernetescommand"; +import * as kubectlConfigMap from "./kubernetesconfigmap"; import * as kubectlSecret from "./kubernetessecret"; import AuthenticationTokenProvider from "docker-common/registryauthenticationprovider/authenticationtokenprovider" @@ -31,47 +32,50 @@ var registryAuthenticationToken = authenticationProvider.getAuthenticationToken( // open kubectl connection and run the command var connection = new ClusterConnection(); -connection.open(tl.getInput("kubernetesServiceEndpoint")).then( - () => run(connection, registryAuthenticationToken), - (err) => tl.setResult(tl.TaskResult.Failed, err.message) -).catch((error) => tl.setResult(tl.TaskResult.Failed, error) ); - +try +{ + connection.open(tl.getInput("kubernetesServiceEndpoint")).then( + () => { return run(connection, registryAuthenticationToken) } + ).then( + () => { + tl.setResult(tl.TaskResult.Succeeded, ""); + connection.close(); + } + ).catch((error) => { + tl.setResult(tl.TaskResult.Failed, error.message) + connection.close(); + }); +} +catch (error) +{ + tl.setResult(tl.TaskResult.Failed, error.message); +} -function run(clusterConnection: ClusterConnection, registryAuthenticationToken: RegistryAuthenticationToken) +async function run(clusterConnection: ClusterConnection, registryAuthenticationToken: RegistryAuthenticationToken) { var secretName = tl.getInput("secretName", false); + var configMapName = tl.getInput("configMapName", false); if(secretName) { - kubectlSecret.run(clusterConnection, registryAuthenticationToken, secretName).fin(function cleanup(){ - clusterConnection.close(); - }).then(function success() { - executeKubectlCommand(clusterConnection); - }, function failure(err) { - tl.setResult(tl.TaskResult.Failed, err.message); - }).done(); + await kubectlSecret.run(clusterConnection, registryAuthenticationToken, secretName); } - else { - executeKubectlCommand(clusterConnection); + + if(configMapName) { + await kubectlConfigMap.run(clusterConnection, configMapName); } + + await executeKubectlCommand(clusterConnection); } // execute kubectl command function executeKubectlCommand(clusterConnection: ClusterConnection) : any { - var command = tl.getInput("command", true); var result = ""; var ouputVariableName = tl.getInput("kubectlOutput", false); - kubectl.run(clusterConnection, command, (data) => result += data) + return kubectl.run(clusterConnection, command, (data) => result += data) .fin(function cleanup() { - clusterConnection.close(); if(ouputVariableName) { tl.setVariable(ouputVariableName, result); } - }) - .then(function success() { - tl.setResult(tl.TaskResult.Succeeded, ""); - }, function failure(err) { - tl.setResult(tl.TaskResult.Failed, err.message); - }) - .done(); + }); } \ No newline at end of file diff --git a/Tasks/Kubernetes/src/kubernetescommand.ts b/Tasks/Kubernetes/src/kubernetescommand.ts index edcddc3a267c..eb74a3af481e 100644 --- a/Tasks/Kubernetes/src/kubernetescommand.ts +++ b/Tasks/Kubernetes/src/kubernetescommand.ts @@ -5,6 +5,7 @@ import * as tr from "vsts-task-lib/toolrunner"; import trm = require('vsts-task-lib/toolrunner'); import * as path from "path"; import * as tl from "vsts-task-lib/task"; +import * as utils from "./utilities"; import ClusterConnection from "./clusterconnection"; export function run(connection: ClusterConnection, kubecommand: string, outputUpdate: (data: string) => any): any { @@ -53,7 +54,7 @@ function getCommandArguments(): string { return tl.getInput("arguments", false); } -function getNameSpace(): string[] { +export function getNameSpace(): string[] { var args: string[] =[]; var namespace = tl.getInput("namespace", false); if(namespace) { diff --git a/Tasks/Kubernetes/src/kubernetesconfigmap.ts b/Tasks/Kubernetes/src/kubernetesconfigmap.ts new file mode 100644 index 000000000000..762ece54ac50 --- /dev/null +++ b/Tasks/Kubernetes/src/kubernetesconfigmap.ts @@ -0,0 +1,79 @@ +"use strict"; + +import tl = require('vsts-task-lib/task'); +import * as tr from "vsts-task-lib/toolrunner"; +import * as kubernetesCommand from "./kubernetescommand"; +import ClusterConnection from "./clusterconnection"; + +export function run(connection: ClusterConnection, configMapName: string): Promise { + if(tl.getBoolInput("forceUpdateConfigMap") == false) + { + return executeKubetclGetConfigmapCommand(connection, configMapName).then(function success() { + tl.debug(tl.loc('ConfigMapExists', configMapName)); + }, function failure() { + return createConfigMap(connection, configMapName); + }); + } + else if(tl.getBoolInput("forceUpdateConfigMap") == true) { + return deleteConfigMap(connection, configMapName).fin(() =>{ + return createConfigMap(connection, configMapName); + }); + } +} + +function deleteConfigMap(connection: ClusterConnection, configMapName: string): any { + tl.debug(tl.loc('DeleteConfigMap', configMapName)); + var command = connection.createCommand(); + command.arg(kubernetesCommand.getNameSpace()); + command.arg("delete") + command.arg("configmap"); + command.arg(configMapName); + var executionOption : tr.IExecOptions = { + silent: true, + failOnStdErr: false, + ignoreReturnCode: true + }; + + return connection.execCommand(command, executionOption); +} + +function getConfigMapArguments(): string { + /* if(tl.getBoolInput("useConfigMapFile") == true) { + var configMapFileOrDirectoryPath = tl.getInput("configMapFile", false); + var configMapFromFromFileArgument: string = ""; + if(configMapFileOrDirectoryPath && tl.exist(configMapFileOrDirectoryPath)) + { + configMapFromFromFileArgument = "--from-file=" + configMapFileOrDirectoryPath; + } + + return configMapFromFromFileArgument; + } else { + + }*/ + + return tl.getInput("configMapArguments", false); +} + +function createConfigMap(connection: ClusterConnection, configMapName: string): any { + tl.debug(tl.loc('CreatingConfigMap', configMapName)); + var command = connection.createCommand(); + command.arg(kubernetesCommand.getNameSpace()); + command.arg("create") + command.arg("configmap"); + command.arg(configMapName); + command.line(getConfigMapArguments()); + return connection.execCommand(command); +} + +function executeKubetclGetConfigmapCommand(connection: ClusterConnection, configMapName: string): any { + tl.debug(tl.loc('GetConfigMap', configMapName)); + var command = connection.createCommand(); + command.arg(kubernetesCommand.getNameSpace()); + command.arg("get") + command.arg("configmap"); + command.arg(configMapName); + var executionOption : tr.IExecOptions = { + silent: true + }; + return connection.execCommand(command, executionOption); +} \ No newline at end of file diff --git a/Tasks/Kubernetes/src/kubernetessecret.ts b/Tasks/Kubernetes/src/kubernetessecret.ts index 4a42f90479b7..3972433db017 100644 --- a/Tasks/Kubernetes/src/kubernetessecret.ts +++ b/Tasks/Kubernetes/src/kubernetessecret.ts @@ -3,6 +3,7 @@ import tl = require('vsts-task-lib/task'); import path = require('path'); import * as tr from "vsts-task-lib/toolrunner"; +import * as kubernetesCommand from "./kubernetescommand"; import ClusterConnection from "./clusterconnection"; import AuthenticationToken from "docker-common/registryauthenticationprovider/registryauthenticationtoken" @@ -33,7 +34,7 @@ function createSecret(connection: ClusterConnection, authenticationToken: Authen function deleteSecret(connection: ClusterConnection, secret: string): any { tl.debug(tl.loc('DeleteSecret', secret)); var command = connection.createCommand(); - command.arg(getNameSpace()); + command.arg(kubernetesCommand.getNameSpace()); command.arg("delete"); command.arg("secret"); command.arg(secret); @@ -52,7 +53,7 @@ function createDockerRegistrySecret(connection: ClusterConnection, authenticatio { tl.debug(tl.loc('CreatingSecret', secret)); var command = connection.createCommand(); - command.arg(getNameSpace()); + command.arg(kubernetesCommand.getNameSpace()); command.arg("create") command.arg("secret"); command.arg("docker-registry"); @@ -76,7 +77,7 @@ function createGenericSecret(connection: ClusterConnection, secret: string): any tl.debug(tl.loc('CreatingSecret', secret)); var command = connection.createCommand(); - command.arg(getNameSpace()); + command.arg(kubernetesCommand.getNameSpace()); command.arg("create") command.arg("secret"); command.arg("generic"); @@ -88,15 +89,4 @@ function createGenericSecret(connection: ClusterConnection, secret: string): any } return connection.execCommand(command); -} - -function getNameSpace(): string[] { - var args: string[] =[]; - var namespace = tl.getInput("namespace", false); - if(namespace) { - args[0] = "-n"; - args[1] = namespace; - } - - return args; } \ No newline at end of file diff --git a/Tasks/Kubernetes/src/utilities.ts b/Tasks/Kubernetes/src/utilities.ts index 6da9709e7ee3..4f0eb0cdc7b2 100644 --- a/Tasks/Kubernetes/src/utilities.ts +++ b/Tasks/Kubernetes/src/utilities.ts @@ -10,7 +10,7 @@ import * as util from "util"; import downloadutility = require("utility-common/downloadutility"); export function getTempDirectory(): string { - return os.tmpdir(); + return tl.getVariable('agent.tempDirectory') || os.tmpdir(); } export function getCurrentTime(): number { diff --git a/Tasks/Kubernetes/task.json b/Tasks/Kubernetes/task.json index 4f0149ea8964..eba7585b7eb0 100644 --- a/Tasks/Kubernetes/task.json +++ b/Tasks/Kubernetes/task.json @@ -13,7 +13,7 @@ "version": { "Major": 0, "Minor": 1, - "Patch": 15 + "Patch": 16 }, "demands": [], "preview": "false", @@ -28,6 +28,11 @@ "displayName": "Secrets", "isExpanded": false }, + { + "name": "configMaps", + "displayName": "ConfigMaps", + "isExpanded": false + }, { "name": "advanced", "displayName": "Advanced", @@ -132,7 +137,7 @@ }, "label": "Arguments", "defaultValue":"", - "helpMarkDown": "Specify keys and literal values to insert in secret.For example, --from-literal=key1=value1 --from-literal=key2=\"top secret\". Please use double quotes to specify any literals that have spaces.", + "helpMarkDown": "Specify keys and literal values to insert in secret.For example, --from-literal=key1=value1 --from-literal=key2=\"top secret\".", "visibleRule": "secretType = generic", "groupName": "secrets" }, @@ -195,6 +200,34 @@ "helpMarkDown": "Delete the secret if it exists and create a new one with updated values.", "groupName": "secrets" }, + { + "name": "configMapName", + "type": "string", + "label": "ConfigMap Name", + "defaultValue": "", + "helpMarkDown": "Name of ConfigMap.", + "groupName": "configMaps" + }, + { + "name": "forceUpdateConfigMap", + "type": "boolean", + "label": "Force update configmap", + "defaultValue": "false", + "helpMarkDown": "Delete the configmap if it exists and create a new one with updated values.", + "groupName": "configMaps" + }, + { + "name": "configMapArguments", + "type": "multiLine", + "properties": { + "resizable": "true", + "rows": "2", + "editorExtension": "ms.vss-services-azure.kubernetes-parameters-grid" + }, + "label": "Arguments", + "helpMarkDown": "Specify keys and literal values to insert in configMap.For example, --from-literal=key1=value1 --from-literal=key2=\"top secret\".", + "groupName": "configMaps" + }, { "name": "versionOrLocation", "type": "radio", @@ -289,6 +322,10 @@ "DownloadingClient": "Downloading kubernetes client.", "CreatingSecret": "Executing create %s secret.", "DeleteSecret": "Executing delete %s secret", + "CreatingConfigMap": "Executing create %s configmap.", + "DeleteConfigMap": "Executing delete %s configmap", + "ConfigMapExists": "ConfigMap %s already exists", + "GetConfigMap": "Executing get %s configmap", "DockerRegistryConnectionNotSpecified": "Docker Registry connection details not specified", "FileNotFoundException": "Can not find file at location: %s", "DownloadingKubeCtlFromUrl": "Downloading Kubectl from Url: %s", diff --git a/Tasks/Kubernetes/task.loc.json b/Tasks/Kubernetes/task.loc.json index 0ce7790f6930..71c170b5d6c0 100644 --- a/Tasks/Kubernetes/task.loc.json +++ b/Tasks/Kubernetes/task.loc.json @@ -13,7 +13,7 @@ "version": { "Major": 0, "Minor": 1, - "Patch": 15 + "Patch": 16 }, "demands": [], "preview": "false", @@ -28,6 +28,11 @@ "displayName": "ms-resource:loc.group.displayName.secrets", "isExpanded": false }, + { + "name": "configMaps", + "displayName": "ms-resource:loc.group.displayName.configMaps", + "isExpanded": false + }, { "name": "advanced", "displayName": "ms-resource:loc.group.displayName.advanced", @@ -201,6 +206,34 @@ "helpMarkDown": "ms-resource:loc.input.help.forceUpdate", "groupName": "secrets" }, + { + "name": "configMapName", + "type": "string", + "label": "ms-resource:loc.input.label.configMapName", + "defaultValue": "", + "helpMarkDown": "ms-resource:loc.input.help.configMapName", + "groupName": "configMaps" + }, + { + "name": "forceUpdateConfigMap", + "type": "boolean", + "label": "ms-resource:loc.input.label.forceUpdateConfigMap", + "defaultValue": "false", + "helpMarkDown": "ms-resource:loc.input.help.forceUpdateConfigMap", + "groupName": "configMaps" + }, + { + "name": "configMapArguments", + "type": "multiLine", + "properties": { + "resizable": "true", + "rows": "2", + "editorExtension": "ms.vss-services-azure.kubernetes-parameters-grid" + }, + "label": "ms-resource:loc.input.label.configMapArguments", + "helpMarkDown": "ms-resource:loc.input.help.configMapArguments", + "groupName": "configMaps" + }, { "name": "versionOrLocation", "type": "radio", @@ -297,6 +330,10 @@ "DownloadingClient": "ms-resource:loc.messages.DownloadingClient", "CreatingSecret": "ms-resource:loc.messages.CreatingSecret", "DeleteSecret": "ms-resource:loc.messages.DeleteSecret", + "CreatingConfigMap": "ms-resource:loc.messages.CreatingConfigMap", + "DeleteConfigMap": "ms-resource:loc.messages.DeleteConfigMap", + "ConfigMapExists": "ms-resource:loc.messages.ConfigMapExists", + "GetConfigMap": "ms-resource:loc.messages.GetConfigMap", "DockerRegistryConnectionNotSpecified": "ms-resource:loc.messages.DockerRegistryConnectionNotSpecified", "FileNotFoundException": "ms-resource:loc.messages.FileNotFoundException", "DownloadingKubeCtlFromUrl": "ms-resource:loc.messages.DownloadingKubeCtlFromUrl",