Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Digger minimal node client backbone #1

Merged
merged 9 commits into from
Dec 5, 2016
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 48 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules
jspm_packages

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

27 changes: 27 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceRoot}/bin/digkins.js",
"cwd": "${workspaceRoot}",
"outFiles": [],
// Replace args with any command you wish for debugging
//"args":["job","create","test"],
"sourceMaps": true
},
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 5858,
"outFiles": [],
"sourceMaps": true
}
]
}
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## AeroGear Digger Node.js client

AeroGear digger node.js command line client.
Create Jenkins job and build your application using Jenkinsfile located in your source code.
For more information about this project go to https://github.com/aerogear/digger-jenkins

## Sample use case

Login to Jenkins. Your credentials will be stored in configuration.
```
digkins login http://myjenkins.com
```

Create job that would use your repository as source code
```
digkins job create my-androip-build https://github.com/android-build/project master
```

Trigger build
```
digkins job build my-androip-build
```

## Supported commands
```
Trigger build for Jenkins job
digkins job build <jobname>

Create jenkins job for git repository with Jenkinsfile
digkins job create <name> [repository] [branch]

Setup jenkins credetials and login into jenkins

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

credetials -> credentials

digkins login <url> [user] [password]

Stream jenkins logs for triggered build
digkins log <job> <queueid>

Generate bash completion script
digkins completion
```
## Development

1. Install node.js
2. Checkout repository
3. Link library to use directly from source code

`npm link .`

16 changes: 16 additions & 0 deletions bin/digkins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env node

var yargonaut = require('yargonaut')
.style('red')
.helpStyle('green')
.errorsStyle('red')
var yargs = require('yargs');

yargs.commandDir("../lib/commands")
.help('help')
.alias('h', 'help')
.strict(true)
.demand(1)
.version()
.completion()
.argv
31 changes: 31 additions & 0 deletions lib/api/createJob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
var fs = require("fs");
var path = require('path');
var _ = require('underscore');
var authHelper = require("../util/auth")

/**
* Create jenkins job
*/
module.exports = (auth, jobArgs, callback) => {
var options = authHelper.createJenkinsOptions(auth.url, auth.user, auth.password);
var jenkins = require('jenkins')(options);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO I would avoid using require anywhere but at the top of the file so that you can easily identify what modules this file depends on.

var fileLocation = path.join(__dirname, '..', '..', 'templates', 'standard.xml');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a Jenkins XML / template file here ?

Copy link
Contributor Author

@wtrocki wtrocki Dec 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's just plain template to create job. We using jenkinsfiles but we would need some simple xml file to create job that would have only giturl and branch configured. Rest would come from Jenkinsfile itself.

I investigated Jenkinfile's automatic job creation and it's really powerful, but for our internal needs we would need to create job explicitly.

var fileContent = fs.readFileSync(fileLocation, 'utf8');
if (fileContent) {
var jobTemplate = _.template(fileContent);
jobXML = jobTemplate(jobArgs);
var options = {
name: jobArgs.name,
xml: jobXML
}
jenkins.job.create(options, function (err, data) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest using arrow function instead, for consistency

callback(err, data);
});
} else {
callback("Standard template is missing!");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning a promise instead of using a callback would increase readability and simplicity. I strongly recommend using them from the beginning, specially when you can use node built-in promises.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josemigallas Let's not start flame wars here.
We had team wide discussions about how we going to use ES2016 and what rules we should have. All of this is here: https://github.com/fheng/best_practice
I'm going to add eslint support soon.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No flame wars at all, just my humble suggestion, don't get me wrong. I read already that repo and it's said nothing about the preferred use of callback/Promise. However, from the last discussion I understood that Promises are preferred.

}

}



14 changes: 14 additions & 0 deletions lib/api/jenkinsInfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
var authUtil = require("../util/auth")

// Retrieve jenkins server information

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an observation: the other doc is written with block format (/** ... */) but here is line format (// ...)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the doc is written in single line format (// ...) but in the other files is with block format (/** ... */) I suggest using always the block format.

module.exports = (auth, callback) => {
var options = authUtil.createJenkinsOptions(auth.url, auth.user, auth.password);
options.crumbIssuer = true;
var jenkins = require('jenkins')(options);
jenkins.info(function (err, data) {
callback(err, data);
});
}



39 changes: 39 additions & 0 deletions lib/api/streamLogs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
var fs = require("fs");
var path = require('path');
var _ = require('underscore');
var authHelper = require("../util/auth");

/**
* Stream jenkins logs
*/
module.exports = (auth, jobName, queueNumber,writter, callback) => {
var options = authHelper.createJenkinsOptions(auth.url, auth.user, auth.password);
var jenkins = require('jenkins')(options);
jenkins.queue.item(queueNumber, function (err, data) {
if (err) {
callback(err);
return;
}
if (!data.executable || !data.executable.number) {
return writter.log("Build did not started yet! Try again later");
}
var buildNo = data.executable.number;

const logStream = jenkins.build.logStream(jobName, buildNo);
writter.log(`## Starting streaming build ${buildNo} log`);
logStream.on('data', function (text) {
writter.log(text);
});
logStream.on('error', function (err) {
writter.error(err);
});

logStream.on('end', function () {
writter.log('Build finished'.gray);
callback(err, data);
});
});
}



18 changes: 18 additions & 0 deletions lib/api/triggerBuild.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
var fs = require("fs");
var path = require('path');
var _ = require('underscore');
var authHelper = require("../util/auth");

/**
* Trigger jenkins build
*/
module.exports = (auth, jobName, callback) => {
var options = authHelper.createJenkinsOptions(auth.url, auth.user, auth.password);
var jenkins = require('jenkins')(options);
jenkins.job.build(jobName, function (err, queueNumber) {
callback(err, queueNumber);
});
}



7 changes: 7 additions & 0 deletions lib/commands/job.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
exports.command = 'job <command>'
exports.desc = 'Manage jenkins jobs'
exports.builder = function (yargs) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, here is a clearer example that the code cleanness would benefit from using arrow functions.

return yargs.commandDir('jobs')
}
exports.handler = function (argv) {
}
28 changes: 28 additions & 0 deletions lib/commands/jobs/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Build jenkins job command
var conf = require("../../util/config");
var logger = require("../../util/logger");
var prompt = require('readline-sync');

var triggerBuild = require("../../api/triggerBuild");

exports.command = 'build <job>'
exports.describe = 'Trigger build for Jenkins job'
exports.builder = (yargs) => {
return yargs.count('verbose').alias('v', 'verbose');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this would looks utterly better on a single line without return.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should be part of the javascript styling tool - something like eslint. Going to add this soon. Any manual styling are hard to enforce and I do not want to make changes for this now until we would have automatic process in place that would fail the build.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I mean is that it should be
yargs => yargs.count('verbose').alias('v', 'verbose');

}

exports.handler = (argv) => {
if (!argv.job) {
argv.job = prompt.question('Jenkins job name: ');
}
triggerBuild(conf.get("auth"), argv.job, function (err, queueNumber) {
if (err) {
logger.error(`Problem when triggering new build for ${argv.job} job`);
if (argv.v) {
logger.error(err);
}
return;
}
logger.info(`Build for job ${argv.job} started! Use "log ${argv.job} ${queueNumber}" command to fetch logs`);
});
};
40 changes: 40 additions & 0 deletions lib/commands/jobs/create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Create jenkins job command
var conf = require("../../util/config");
var logger = require("../../util/logger");
var prompt = require('readline-sync');

var createJobApi = require("../../api/createJob");

exports.command = 'create <name> [repository] [branch]'
exports.aliases = ["new"];
exports.describe = 'Create jenkins job for git repository with Jenkinsfile'
exports.builder = (yargs) => {
return yargs.count('verbose').alias('v', 'verbose');
}

exports.handler = (argv) => {
if (!argv.repository) {
argv.user = prompt.question('Git repository to use: ');
}
if (!argv.branch) {
argv.password = prompt.question('Git branch to checkout: ');
}
createJobApi(conf.get("auth"), {
name: argv.name,
repository: argv.repository,
branch: argv.branch
}, function (err, data) {
if (err) {
logger.error(`Problem when creating ${argv.name} job`);
if (argv.verbose) {
logger.error(err);
}
return;
}
logger.info(`Job ${argv.name} created!`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This indentation is one step further making it less readable.

if (argv.verbose) {
logger.debug(data);
}
});
};

40 changes: 40 additions & 0 deletions lib/commands/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Login into jenkins command
var conf = require("../util/config");
var logger = require("../util/logger");
var prompt = require('readline-sync');

var infoApi = require("../api/jenkinsInfo");

exports.command = 'login <url> [user] [password]'
exports.aliases = ["setup", "init"];
exports.describe = 'Setup jenkins credetials and login into jenkins'
exports.builder = (yargs) => {
return yargs.count('verbose').alias('v', 'verbose');
}
exports.handler = (argv) => {
if (!argv.user) {
argv.user = prompt.question('Username: ');
}
if (!argv.password) {
argv.password = prompt.question('Password: ',{ hideEchoBack: true});
}
var auth = {
url: argv.url,
user: argv.user,
password: argv.password
}
infoApi(auth, function (err, data) {
if(err){
logger.info("Cannot connect to jenkins instance", err);
return;
}
if (data) {
logger.info("Login succesfull! Connected to jenkins!");
conf.set("auth", auth);
if (argv.verbose) {
logger.debug(JSON.stringify(data, null, 4));
}
}
});
};