Skip to content

Commit 1340745

Browse files
Saas 7153 prevent runner name collisions (#458)
1 parent 70cbfbe commit 1340745

File tree

4 files changed

+68
-24
lines changed

4 files changed

+68
-24
lines changed

lib/interface/cli/commands/hybrid/init.cmd.js

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
const Command = require('../../Command');
33
const runnerRoot = require('../root/runner.cmd');
44
const inquirer = require('inquirer');
5-
const { getAllKubeContexts, getKubeContext } = require('../../helpers/kubernetes');
5+
const { getAllKubeContexts, getKubeContext, getAllNamespaces } = require('../../helpers/kubernetes');
66
const installAgent = require('../agent/install.cmd');
77
const pipelinesRunCmd = require('../pipeline/run.cmd');
88
const installMonitoring = require('../monitor/install.cmd');
@@ -18,7 +18,7 @@ const INSTALLATION_DEFAULTS = {
1818
NAMESPACE: 'codefresh',
1919
MAKE_DEFAULT_RE: true,
2020
RUN_DEMO_PIPELINE: true,
21-
DEMO_PIPELINE_NAME: 'Hello Codefresh',
21+
DEMO_PIPELINE_NAME: 'Codefresh-Runner Demo',
2222
CF_CONTEXT_NAME: 'cf-runner',
2323
};
2424

@@ -29,7 +29,12 @@ function prettyError(error) {
2929
if (typeof errObj === 'string') {
3030
errObj = JSON.parse(errObj);
3131
}
32-
return _.get(errObj, 'message', error);
32+
33+
if (!errObj.message) {
34+
return error;
35+
}
36+
37+
return errObj.code ? `${errObj.message} [code: ${errObj.code}]` : errObj.message;
3338
} catch (e) {
3439
return _.get(error, 'message', JSON.stringify(error));
3540
}
@@ -45,8 +50,8 @@ async function createDemoPipeline(runtimeName) {
4550
pipeline.spec.steps.test = {
4651
stage: 'test',
4752
title: 'test',
48-
image: 'ubuntu:latest',
49-
commands: ['echo hello codefresh'],
53+
image: 'alpine:latest',
54+
commands: ['echo hello codefresh runner!'],
5055
};
5156

5257
await sdk.pipelines.replace(
@@ -83,14 +88,33 @@ async function createAndExecuteDemoPipeline(runtimeName) {
8388
console.log(`Demo pipeline with the name: "${colors.cyan(INSTALLATION_DEFAULTS.DEMO_PIPELINE_NAME)}" already exists`);
8489
}
8590

86-
console.log(`${colors.yellow('*NOTE* Running a pipeline for the first time might take a longer than usual')}`);
91+
console.log(`${colors.yellow('*NOTE* Running a pipeline for the first time might take longer than usual.')}`);
8792
console.log(`Executing pipeline "${colors.cyan(INSTALLATION_DEFAULTS.DEMO_PIPELINE_NAME)}"`);
8893
await pipelinesRunCmd.handler({
8994
name: INSTALLATION_DEFAULTS.DEMO_PIPELINE_NAME,
9095
exitProcess: false,
9196
});
9297
}
9398

99+
async function getRecommendedKubeNamespace(kubeconfigPath, kubeContextName) {
100+
const defaultName = INSTALLATION_DEFAULTS.NAMESPACE;
101+
const namespaces = await getAllNamespaces(kubeconfigPath, kubeContextName);
102+
let name;
103+
104+
if (!_.isArray(namespaces) || !_.find(namespaces, ns => ns === defaultName)) {
105+
name = defaultName; // use the default name if there are no collisions
106+
} else {
107+
const namespacesSet = new Set(namespaces); // for fast lookup
108+
let i = 1;
109+
while (namespacesSet.has(`${defaultName}-${i}`)) {
110+
i += 1;
111+
}
112+
name = `${defaultName}-${i}`;
113+
}
114+
115+
return name;
116+
}
117+
94118
const initCmd = new Command({
95119
root: false,
96120
parent: runnerRoot,
@@ -194,28 +218,30 @@ const initCmd = new Command({
194218
if (noQuestions) {
195219
// set defaults
196220
kubeContextName = getKubeContext(kubeConfigPath);
197-
kubeNamespace = INSTALLATION_DEFAULTS.NAMESPACE;
221+
kubeNamespace = await getRecommendedKubeNamespace(kubeConfigPath, kubeContextName);
198222
shouldMakeDefaultRe = INSTALLATION_DEFAULTS.MAKE_DEFAULT_RE;
199223
shouldExecutePipeline = INSTALLATION_DEFAULTS.RUN_DEMO_PIPELINE;
200224
} else {
201-
const questions = [];
225+
console.log(colors.green('This installer will guide you through the Codefresh Runner installation process'));
202226
if (!kubeContextName && !noQuestions) {
203227
const contexts = getAllKubeContexts(kubeConfigPath);
204228
const currentKubeContext = getKubeContext(kubeConfigPath);
205229

206-
questions.push({
230+
const answer = await inquirer.prompt({
207231
type: 'list',
208232
name: 'context',
209233
message: 'Name of Kubernetes context to use',
210234
default: currentKubeContext,
211235
choices: contexts,
212236
});
237+
kubeContextName = answer.context;
213238
}
239+
const questions = [];
214240
if (!kubeNamespace && !noQuestions) {
215241
questions.push({
216242
type: 'input',
217243
name: 'namespace',
218-
default: INSTALLATION_DEFAULTS.NAMESPACE,
244+
default: await getRecommendedKubeNamespace(kubeConfigPath, kubeContextName),
219245
message: 'Kubernetes namespace to install into (will be created if it does not exist)',
220246
validate: value => (value !== undefined && value !== '') || 'Please enter namespace\'s name',
221247
});
@@ -239,19 +265,18 @@ const initCmd = new Command({
239265
});
240266
}
241267

242-
console.log(colors.green('This installer will guide you through the Codefresh Runner installation process'));
243268
const answers = await inquirer.prompt(questions);
244269
kubeContextName = kubeContextName || answers.context;
245270
kubeNamespace = kubeNamespace || answers.namespace;
246-
shouldMakeDefaultRe = shouldMakeDefaultRe || answers.shouldMakeDefaultRe;
247-
shouldExecutePipeline = shouldExecutePipeline || answers.shouldExecutePipeline;
271+
shouldMakeDefaultRe = _.isUndefined(shouldMakeDefaultRe) ? answers.shouldMakeDefaultRe : shouldMakeDefaultRe;
272+
shouldExecutePipeline = _.isUndefined(shouldExecutePipeline) ? answers.shouldExecutePipeline : shouldExecutePipeline;
248273
}
249274

250275
console.log(`\n${colors.green('Installation options summary:')}
251276
1. Kubernetes Context: ${colors.cyan(kubeContextName)}
252277
2. Kubernetes Namespace: ${colors.cyan(kubeNamespace)}
253-
3. Set this as default account runtime-environment: ${colors.cyan(shouldMakeDefaultRe)}
254-
4. Execute demo pipeline after install: ${colors.cyan(shouldExecutePipeline)}
278+
3. Set this as default account runtime-environment: ${colors.cyan(!!shouldMakeDefaultRe)}
279+
4. Execute demo pipeline after install: ${colors.cyan(!!shouldExecutePipeline)}
255280
`);
256281

257282
if (token) { // Add context

lib/interface/cli/helpers/kubernetes.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const path = require('path');
2-
const { KubeConfig } = require('kubernetes-client');
2+
const { KubeConfig, Client } = require('kubernetes-client');
3+
const Request = require('kubernetes-client/backends/request');
4+
const _ = require('lodash');
35

46
const getKubeContext = (kubeconfigPath) => {
57
// eslint-disable-next-line global-require
@@ -23,8 +25,25 @@ const getAllKubeContexts = (kubeconfigPath) => {
2325
}, []);
2426
}
2527
};
28+
const getAllNamespaces = async (kubeconfigPath, kubeContextName) => {
29+
// eslint-disable-next-line global-require
30+
const homedir = require('os').homedir();
31+
const kubePath = kubeconfigPath || process.env.KUBECONFIG || path.join(homedir, '.kube', 'config');
32+
const kubeconfig = new KubeConfig();
33+
kubeconfig.loadFromFile(kubePath);
34+
kubeconfig.setCurrentContext(kubeContextName);
35+
const backend = new Request({ kubeconfig });
36+
const client = new Client({ backend, version: 1.13 });
37+
const resp = await client.api.v1.namespaces.get();
38+
if (resp.statusCode === 200) {
39+
const nsObjs = _.get(resp, 'body.items');
40+
return _.map(nsObjs, ns => _.get(ns, 'metadata.name'));
41+
}
42+
return [];
43+
};
2644

2745
module.exports = {
2846
getKubeContext,
2947
getAllKubeContexts,
48+
getAllNamespaces,
3049
};

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codefresh",
3-
"version": "0.60.2",
3+
"version": "0.62.0",
44
"description": "Codefresh command line utility",
55
"main": "index.js",
66
"preferGlobal": true,
@@ -55,7 +55,7 @@
5555
"js-yaml": "^3.10.0",
5656
"jsonwebtoken": "^8.1.0",
5757
"kefir": "^3.8.1",
58-
"kubernetes-client": "^8.3.6",
58+
"kubernetes-client": "^9.0.0",
5959
"lodash": "^4.17.4",
6060
"mkdirp": "^0.5.1",
6161
"moment": "^2.19.4",

yarn.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4164,10 +4164,10 @@ kleur@^2.0.1:
41644164
resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300"
41654165
integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==
41664166

4167-
kubernetes-client@^8.3.6:
4168-
version "8.3.7"
4169-
resolved "https://registry.yarnpkg.com/kubernetes-client/-/kubernetes-client-8.3.7.tgz#017692c7cfcc2c142502bad8dee9c6e92c57c4f2"
4170-
integrity sha512-A0rvfQAvwAuPTooBOSErpTcnwcQxhkmawjOm/gUdGDWCUZoYmAVgVGFnc/klda+X1tvHwleavDsLqmqaYscH2w==
4167+
kubernetes-client@^9.0.0:
4168+
version "9.0.0"
4169+
resolved "https://registry.yarnpkg.com/kubernetes-client/-/kubernetes-client-9.0.0.tgz#f72e6c71aaa20548b3d6466f1dc88dfa61fb3ba4"
4170+
integrity sha512-Qy8o42dZVHB9P+cIiKdWpQbz/65l/qW1fDYvlzzeSLftmL1Ne3HEiM+0TmKAwNuRW0pTJN2tRWhcccToclxJ8g==
41714171
dependencies:
41724172
"@kubernetes/client-node" "0.10.2"
41734173
camelcase "^6.0.0"
@@ -4179,7 +4179,7 @@ kubernetes-client@^8.3.6:
41794179
pump "^3.0.0"
41804180
qs "^6.9.0"
41814181
request "^2.88.2"
4182-
swagger-fluent "^5.0.1"
4182+
swagger-fluent "^5.0.3"
41834183
url-join "^4.0.1"
41844184
ws "^7.2.3"
41854185

@@ -6513,7 +6513,7 @@ swagger-client@^3.8.22:
65136513
utf8-bytes "0.0.1"
65146514
utfstring "^2.0.0"
65156515

6516-
swagger-fluent@^5.0.1:
6516+
swagger-fluent@^5.0.3:
65176517
version "5.0.3"
65186518
resolved "https://registry.yarnpkg.com/swagger-fluent/-/swagger-fluent-5.0.3.tgz#48564e1ae4f3430488b00be40ffeab257a6f14c0"
65196519
integrity sha512-i43ADMtPi7dxAN75Lw50SlncMB31FgaVwXqKioR8SWs+Yon2RbiLU1J1PGMXA4N8cSt9Vz5RHzaoKjz/+iW88g==

0 commit comments

Comments
 (0)