You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2020/04/17 15:21:20 UTC

[sling-whiteboard] 01/03: Commit changes since initial contribution.

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git

commit a2187c2491127a6011c9b5fe8d439ccf4aa1fc93
Author: Henry Saginor <hs...@gmail.com>
AuthorDate: Thu Apr 16 14:50:46 2020 -0700

    Commit changes since initial contribution.
---
 slingpackager/cmds/{delete.js => build.js}      |   6 +-
 slingpackager/cmds/delete.js                    |   2 +-
 slingpackager/cmds/install.js                   |   2 +-
 slingpackager/cmds/list.js                      |   2 +-
 slingpackager/cmds/test.js                      |  18 ++
 slingpackager/cmds/uninstall.js                 |   2 +-
 slingpackager/cmds/upload.js                    |   2 +-
 slingpackager/package-lock.json                 |   2 +-
 slingpackager/package.json                      |   4 +-
 slingpackager/test/slingpackager.tests.js       |  17 ++
 slingpackager/test/testContent-1.0-SNAPSHOT.zip | Bin 2680 -> 0 bytes
 slingpackager/utils/aempackager.js              | 197 ++++++++++-------
 slingpackager/utils/composumpackager.js         | 278 ++++++++++++++++--------
 13 files changed, 350 insertions(+), 182 deletions(-)

diff --git a/slingpackager/cmds/delete.js b/slingpackager/cmds/build.js
similarity index 87%
copy from slingpackager/cmds/delete.js
copy to slingpackager/cmds/build.js
index be297e4..d2b18be 100644
--- a/slingpackager/cmds/delete.js
+++ b/slingpackager/cmds/build.js
@@ -19,8 +19,8 @@
 const packager = require('../utils/packager')
 const logger = require('../utils/consoleLogger')
 
-exports.command = 'delete <package>'
-exports.desc = 'delete package on server'
+exports.command = 'build <package>'
+exports.desc = 'build package on server'
 exports.handler = (argv) => {
   let user = argv.user.split(':');
   let userName = user[0];
@@ -32,7 +32,7 @@ exports.handler = (argv) => {
   logger.init(argv);
   packager.test(argv, (success, packageManager) => {
     if(success) {
-        packageManager.deletePackage(argv.server, userName, pass, argv.package);
+        packageManager.buildPackage(argv.server, userName, pass, argv.package, argv.retry);
     }
   });
 
diff --git a/slingpackager/cmds/delete.js b/slingpackager/cmds/delete.js
index be297e4..7f9f56f 100644
--- a/slingpackager/cmds/delete.js
+++ b/slingpackager/cmds/delete.js
@@ -32,7 +32,7 @@ exports.handler = (argv) => {
   logger.init(argv);
   packager.test(argv, (success, packageManager) => {
     if(success) {
-        packageManager.deletePackage(argv.server, userName, pass, argv.package);
+        packageManager.deletePackage(argv.server, userName, pass, argv.package, argv.retry);
     }
   });
 
diff --git a/slingpackager/cmds/install.js b/slingpackager/cmds/install.js
index c01831e..1dc0817 100644
--- a/slingpackager/cmds/install.js
+++ b/slingpackager/cmds/install.js
@@ -32,7 +32,7 @@ exports.handler = (argv) => {
   logger.init(argv);
   packager.test(argv, (success, packageManager) => {
     if(success) {
-        packageManager.installPackage(argv.server, userName, pass, argv.package);
+        packageManager.installPackage(argv.server, userName, pass, argv.package, argv.retry);
     }
   });
 
diff --git a/slingpackager/cmds/list.js b/slingpackager/cmds/list.js
index ffad50f..6f40dab 100644
--- a/slingpackager/cmds/list.js
+++ b/slingpackager/cmds/list.js
@@ -33,7 +33,7 @@ exports.handler = (argv) => {
 
   packager.test(argv, (success, packageManager) => {
     if(success) {
-        packageManager.list(argv.server, userName, pass);
+        packageManager.list(argv.server, userName, pass,  argv.retry);
     }
   });
 
diff --git a/slingpackager/cmds/test.js b/slingpackager/cmds/test.js
index ab2c208..dc53901 100644
--- a/slingpackager/cmds/test.js
+++ b/slingpackager/cmds/test.js
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 const packager = require('../utils/packager')
 const logger = require('../utils/consoleLogger')
 
diff --git a/slingpackager/cmds/uninstall.js b/slingpackager/cmds/uninstall.js
index 23599b4..940d1bb 100644
--- a/slingpackager/cmds/uninstall.js
+++ b/slingpackager/cmds/uninstall.js
@@ -32,7 +32,7 @@ exports.handler = (argv) => {
   logger.init(argv);
   packager.test(argv, (success, packageManager) => {
     if(success) {
-        packageManager.uninstallPackage(argv.server, userName, pass, argv.package);
+        packageManager.uninstallPackage(argv.server, userName, pass, argv.package,  argv.retry);
     }
   });
 
diff --git a/slingpackager/cmds/upload.js b/slingpackager/cmds/upload.js
index 5bafcb5..4a22552 100644
--- a/slingpackager/cmds/upload.js
+++ b/slingpackager/cmds/upload.js
@@ -53,7 +53,7 @@ exports.handler = (argv) => {
 
   packager.test(argv, (success, packageManager) => {
     if(success) {
-        packageManager.uploadPackage(argv.server, userName, pass, packagePath, argv.install);
+        packageManager.uploadPackage(argv.server, userName, pass, packagePath, argv.install, argv.retry);
     }
   });
 
diff --git a/slingpackager/package-lock.json b/slingpackager/package-lock.json
index 278adc6..fed1d9a 100644
--- a/slingpackager/package-lock.json
+++ b/slingpackager/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "@apachesling/slingpackager",
-  "version": "0.0.3",
+  "version": "0.0.4",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/slingpackager/package.json b/slingpackager/package.json
index c5a4a34..4962262 100644
--- a/slingpackager/package.json
+++ b/slingpackager/package.json
@@ -1,11 +1,11 @@
 {
   "name": "@apachesling/slingpackager",
-  "version": "0.0.3",
+  "version": "0.0.4",
   "description": "prepare a sling package file for sling and install it",
   "main": "index.js",
   "repository": {
     "type": "git",
-    "url": "git+https://github.com/peregrine-cms/slingpackager.git"
+    "url": "git+https://github.com/hsaginor/sling-whiteboard.git"
   },
   "scripts": {
     "test": "mocha"
diff --git a/slingpackager/test/slingpackager.tests.js b/slingpackager/test/slingpackager.tests.js
index 94962b7..dc70cf6 100644
--- a/slingpackager/test/slingpackager.tests.js
+++ b/slingpackager/test/slingpackager.tests.js
@@ -41,6 +41,8 @@ const password = serverInfo.password;
 
 describe('slingpackager', function() {
 
+    this.timeout(30000);
+    
     // package test
     describe('package', function() {
         it('should create package', function() {
@@ -69,6 +71,13 @@ describe('slingpackager', function() {
         });
     });
 
+    // build test
+    describe('build', function() {
+        it('should build package', function(done) {
+            testBuild(done);
+        });
+    });
+
     // uninstall test
     describe('uninstall', function() {
         it('should uninstall package', function(done) {
@@ -118,6 +127,14 @@ function testInstall(done) {
     assert200(server + testInstallPath, done);
 };
 
+// build command test
+function testBuild(done) {
+    var cmd = 'node bin/slingpackager build ' + packServerName + ' -s ' + server;
+    var output = exec(cmd);
+    logger.debug(output);
+    assert200(server + testInstallPath, done);
+};
+
 // uninstall command test
 function testUninstall(done) {
     var cmd = 'node bin/slingpackager uninstall ' + packServerName + ' -s ' + server;
diff --git a/slingpackager/test/testContent-1.0-SNAPSHOT.zip b/slingpackager/test/testContent-1.0-SNAPSHOT.zip
deleted file mode 100644
index d65de1c..0000000
Binary files a/slingpackager/test/testContent-1.0-SNAPSHOT.zip and /dev/null differ
diff --git a/slingpackager/utils/aempackager.js b/slingpackager/utils/aempackager.js
index 8f72f48..c30f015 100644
--- a/slingpackager/utils/aempackager.js
+++ b/slingpackager/utils/aempackager.js
@@ -41,36 +41,24 @@ const checkService = (url, username, password, callback) => {
     logger.debug('request =', JSON.stringify(req.toJSON()));
 }
 
-const list = (url, username, password) => {
+const list = (url, username, password, maxRetry) => {
   logger.log('Listing packages on', url)
 
   let serviceURL = url + endpoint + '?cmd=ls';
-  logger.debug('Service call: ', serviceURL);
-  let post = request.post({url: serviceURL}, (error, response, body) => {
+  var post = callService({serviceURL, username, password, maxRetry}, (error, result) => {
     if(error) {
       logger.error(error);
+      process.exit(1);
     }
 
-    if(response && response.statusCode===200) {
-      // logger.log(body);
-      xml2js.parseString(body, (error, result) => {
-        if(result) {
-          var data = getData(result);
-          displayPackages(data[0].packages[0].package);
-        } else if(error) {
-          logger.error(error);
-        }
-      });
-    } else {
-      logger.error('Unable to connect to server. statusCode:', response && response.statusCode);
+    if(result) {
+      var data = getData(result);
+      displayPackages(data[0].packages[0].package);
     }
-
-  }).auth(username, password);
-
-  logger.debug(JSON.stringify(post.toJSON()));
+  });
 }
 
-const uploadPackage = (url, username, password, packagePath, install) => {
+const uploadPackage = (url, username, password, packagePath, install, maxRetry) => {
   logger.log('Uploading AEM package',packagePath,'on', url);
 
   let serviceURL = url + endpoint;
@@ -78,86 +66,124 @@ const uploadPackage = (url, username, password, packagePath, install) => {
     serviceURL = serviceURL + '?install=true'
   }
 
-  logger.debug('Service call: ', serviceURL);
-  var post = request.post({url: serviceURL}, (error, response, body) => {
-    if(error) {
-      logger.error(error);
-    }
-
-    if(response && response.statusCode===200) {
-      xml2js.parseString(body, (error, result) => {
-        if(result) {
-          if(getStatusCode(result) === '200') {
-            logger.log("Done!");
-          } else {
-            logger.error("Something went wrong! Check server logs.");
-          }
-        } else if(error) {
-          logger.error(error);
-        }
-        logger.debug(body);
-      });
-    } else {
-      logger.error('Unable to connect to server. statusCode:', response && response.statusCode);
-    }
-
-  }).auth(username, password);
+  var post = executeCommand(serviceURL, username, password, maxRetry);
   post.form().append('file', fs.createReadStream(packagePath));
-  logger.debug(JSON.stringify(post.toJSON()));
 }
 
-const deletePackage = (url, username, password, package) => {
+const deletePackage = (url, username, password, package, maxRetry) => {
   logger.log('Deleting AEM package',package,'on', url);
-  let post = executePackageCommand(url, username, password, package, 'rm');
-  logger.debug(JSON.stringify(post.toJSON()));
+  executePackageCommand(url, username, password, package, 'rm', maxRetry);
 }
 
-const installPackage = (url, username, password, package) => {
+const installPackage = (url, username, password, package, maxRetry) => {
   logger.log('Installing AEM package',package,'on', url);
-  let post = executePackageCommand(url, username, password, package, 'inst');
-  logger.debug(JSON.stringify(post.toJSON()));
+  executePackageCommand(url, username, password, package, 'inst', maxRetry);
 }
 
-const uninstallPackage = (url, username, password, package) => {
+const uninstallPackage = (url, username, password, package, maxRetry) => {
   logger.log('Uninstalling AEM package',package,'on', url);
-  let post = executePackageCommand(url, username, password, package, 'uninst');
-  logger.debug(JSON.stringify(post.toJSON()));
+  executePackageCommand(url, username, password, package, 'uninst', maxRetry);
+}
+
+const buildPackage = (url, username, password, package, maxRetry) => {
+  logger.log('Building AEM package', package, 'on', url);
+  executePackageCommand(url, username, password, package, 'build', maxRetry);
 }
 
 const getName = () => {
   return 'AEM Package Manager';
 }
 
-function executePackageCommand(url, username, password, packageName, cmd) {
+function executePackageCommand(url, username, password, packageName, cmd, maxRetry) {
   let serviceURL = url + endpoint + '?cmd=' + cmd;
-  logger.debug('Service call: ', serviceURL);
-  var post = request.post({url: serviceURL}, (error, response, body) => {
+  var post = executeCommand(serviceURL, username, password, maxRetry);
+  post.form().append('name', packageName);
+  return post;
+}
+
+function executeCommand(serviceURL, username, password, maxRetry) {
+  var post = callService({serviceURL, username, password, maxRetry}, (error, result) => {
     if(error) {
       logger.error(error);
+      process.exit(1);
     }
 
-    if(response && response.statusCode===200) {
-      xml2js.parseString(body, (error, result) => {
-        if(result) {
-          if(getStatusCode(result) === '200') {
-            logger.log("Done!");
-          } else {
-            logger.log(getStatusText(result));
-          }
-        } else if(error) {
-          logger.error(error);
-        }
-      });
-    } else {
-      logger.error('Unable to connect to server. statusCode:', response && response.statusCode);
+    if(result) {
+      var respLog = getResponseLog(result);
+      if(respLog) {
+        logger.log(respLog);
+      } else {
+        logger.log(getStatusText(result));
+      }
     }
-
-  }).auth(username, password);
-  post.form().append('name', packageName);
+  });
 
   return post;
 }
 
+function callService(data, callback) {
+  if(data.retryCount === undefined) {
+      data.retryCount = 0;
+      if(data.maxRetry === undefined) {
+          data.maxRetry = 10;
+      }
+  }
+
+  logger.debug(data.retryCount + '. Service call: ', data.serviceURL);
+
+  let req = request.post({ url: data.serviceURL }, (error, response, body) => {
+      var statusCodeLine = (response === undefined) ? "" : "Response: " + response.statusCode + " : " + response.statusMessage;
+      logger.debug(statusCodeLine);
+
+      if (error) {
+          if(data.retryCount < data.maxRetry) {
+              data.retryCount++;
+              callService(data, callback);
+          } else  { 
+              logger.error(error);
+              callback(error + " " + statusCodeLine, undefined); 
+          }
+
+          return;
+      } else if(response && response.statusCode===200) {
+        xml2js.parseString(body, (error, result) => {
+          if(result) {
+            logger.debug('Response body: ',body);
+            if(getStatusCode(result) === '200') {
+              callback(undefined, result);
+            } else {
+              logger.debug(body);
+              logger.warn("Response status:",getStatusCode(result),":",getStatusText(result));
+              retryCallService(data, 'Unable to parse service response for', data.serviceURL, callback);
+            }
+          } else if(error) {
+            logger.debug(body);
+            logger.warn('Unable to parse service response for', data.serviceURL);
+            retryCallService(data, 'Unable to parse service response for', data.serviceURL, callback);
+          }
+        });
+
+        return;
+      } 
+
+      retryCallService(data, "Error calling service " + data.serviceURL, callback)
+
+      return;
+  }).auth(data.username, data.password);
+
+  logger.debug(JSON.stringify(req.toJSON()));
+  return req;
+}
+
+function retryCallService(data, error, callback) {
+  if(data.retryCount < data.maxRetry) {
+    data.retryCount++;
+    callService(data, callback);
+   } else  { 
+    callback(error, undefined);
+   }
+}
+
 function displayPackages(packages) {
   for(var i=0; i<packages.length; i++) {
     logger.log('name='+packages[i].name[0]+
@@ -168,11 +194,29 @@ function displayPackages(packages) {
 }
 
 function getStatusCode(result) {
-  return result.crx.response[0].status[0].$.code;
+  try {
+    return result.crx.response[0].status[0].$.code;
+  } catch(e) {
+    return undefined;
+  }
 }
 
 function getStatusText(result) {
-  return result.crx.response[0].status[0]._;
+  try {
+    return result.crx.response[0].status[0]._;
+  } catch(e) {
+    return undefined;
+  }
+}
+
+function getResponseLog(result) {
+  if(result.crx.response[0].log) {
+    return result.crx.response[0].log;
+  }
+  if(result.crx.response[0].data && result.crx.response[0].data[0].log) {
+    return result.crx.response[0].data[0].log;
+  } 
+  return undefined;
 }
 
 function getData(result) {
@@ -186,6 +230,7 @@ module.exports = {
     deletePackage,
     installPackage,
     uninstallPackage,
+    buildPackage,
     getName
 }
 
diff --git a/slingpackager/utils/composumpackager.js b/slingpackager/utils/composumpackager.js
index 4b4817b..da3c817 100644
--- a/slingpackager/utils/composumpackager.js
+++ b/slingpackager/utils/composumpackager.js
@@ -41,153 +41,117 @@ const checkService = (url, username, password, callback) => {
     }).auth(username, password);
 }
 
-const list = (url, username, password) => {
+const list = (url, username, password, maxRetry) => {
     logger.log('Listing packages on', url);
-    listPackages(url, username, password, '');
+    listPackages(url, username, password, '', maxRetry);
 }
 
-const uploadPackage = (url, username, password, packagePath, install) => {
+const uploadPackage = (url, username, password, packagePath, install, maxRetry) => {
     logger.log('Uploading package', packagePath, 'on', url);
 
     let serviceURL = url + uploadEndpoint;
-    logger.debug('Service call: ', serviceURL);
-    let post = request.post({ url: serviceURL }, (error, response, body) => {
-        if (error) {
+    let post = callPostService({serviceURL, username, password, maxRetry}, (error, json) => {
+        if(error) {
+            logger.error('Unable to upload package', packagePath);
             logger.error(error);
-        }
-
-        if (response && response.statusCode === 200) {
-            var json = JSON.parse(body);
+            process.exit(1);
+        } else {
             logger.log(json.status)
             logger.debug(JSON.stringify(json));
 
             if(install) {
-                logger.debug('installing', json.path);
-                installPackage(url, username, password, json.path);
+                logger.info('installing', json.path);
+                installPackage(url, username, password, json.path, maxRetry);
             }
-
-        } else {
-            logger.error('Unable to upload package. statusCode:', response && response.statusCode);
-            logger.debug(body);
         }
-    }).auth(username, password);
+    });
 
     post.form().append('file', fs.createReadStream(packagePath));
 
     logger.debug(JSON.stringify(post.toJSON()));
 }
 
-const deletePackage = (url, username, password, package) => {
+const deletePackage = (url, username, password, package, maxRetry) => {
     logger.log('Deleting package', package, 'on', url);
 
     let serviceURL = url + deleteEndpoint + package;
-    logger.debug('Service call: ', serviceURL);
-    let req = request({ url: serviceURL, method: 'DELETE' }, (error, response, body) => {
-        if (error) {
+    let req = callService({serviceURL, method: 'DELETE', username, password, maxRetry}, (error, json) => {
+        if(error) {
+            logger.error('Unable to delete package', package);
             logger.error(error);
-        }
-
-        if (response && response.statusCode === 200) {
-            if (body) {
-                var json = JSON.parse(body);
-                logger.log(json.status)
-            } else {
-                logger.error('Unable to delete package. Check package name (try by path).');
-            }
+            process.exit(1);
         } else {
-            logger.error('Unable to delete package. statusCode:', response && response.statusCode);
-            logger.debug(body);
+            logger.log(json.status);
+            logger.debug(JSON.stringify(json));
         }
-    }).auth(username, password);
+    });
 
     logger.debug(JSON.stringify(req.toJSON()));
 }
 
-const installPackage = (url, username, password, package) => {
+const installPackage = (url, username, password, package, maxRetry) => {
     logger.log('Installing package', package, 'on', url);
 
-    let serviceURL = url + installEndpoint + package;
-    logger.debug('Service call: ', serviceURL);
-    let post = request.post({ url: serviceURL }, (error, response, body) => {
-        if (error) {
+    let post = postJob({url, username, password, package, maxRetry}, 'install', (error, result) => {
+        if(error) {
+            logger.error('Unable to uninstall package', package);
             logger.error(error);
-        }
-
-        if (response && response.statusCode === 200) {
-            if (body) {
-                var json = JSON.parse(body);
-                logger.log(json.status)
-            }
-        } else {
-            logger.error('Unable to install package. statusCode:', response && response.statusCode);
-            logger.debug(body);
-        }
-    }).auth(username, password);
-
-    logger.debug(JSON.stringify(post.toJSON()));
+            process.exit(1);
+        } 
+    });
 }
 
-const uninstallPackage = (url, username, password, package) => {
+const uninstallPackage = (url, username, password, package, maxRetry) => {
     logger.log('Uninstalling package', package, 'on', url);
 
-    let serviceURL = url + '/bin/cpm/core/jobcontrol.job.json';
-    logger.debug('Service call: ', serviceURL);
-    // Commented out code bellow does not work wirh Composum 1.7/Sling9
-    // var post = request.post({ url: url + uninstallEndpoint + package }, (error, response, body) => {
-    var post = request.post({ url: serviceURL }, (error, response, body) => {
-        if (error) {
+    let post = postJob({url, username, password, package, maxRetry}, 'uninstall', (error, result) => {
+        if(error) {
+            logger.error('Unable to uninstall package', package);
             logger.error(error);
-        }
-
-        if (response && response.statusCode === 200) {
-            if (body) {
-                var json = JSON.parse(body);
-                logger.log('done');
-                // Commented out for Composum 1.7/Sling9
-                // logger.log(json.status)
-            }
+            process.exit(1);
         } else {
-            logger.error('Unable to uninstall package. statusCode:', response && response.statusCode);
-            logger.debug(body);
+            if(result && (typeof(result) === 'string') && result.startsWith('Unable')) {
+                logger.error(result);
+                process.exit(1);
+            }
         }
-    }).auth(username, password);
+    });
+}
 
-    // These parameters are not needed when using uninstallEndpoint with Sling11.
-    // This is a workaround for Composum 1.7 and Sling9 which does not have this endpoint.
-    var form = post.form();
-    form.append('event.job.topic', 'com/composum/sling/core/pckgmgr/PackageJobExecutor');
-    form.append('reference', package);
-    form.append('_charset_', 'UTF-8');
-    form.append('operation', 'uninstall');
+const buildPackage = (url, username, password, package, maxRetry) => {
+    logger.log('Building package', package, 'on', url);
 
-    logger.debug(JSON.stringify(post.toJSON()));
+    let post = postJob({url, username, password, package, maxRetry}, 'assemble', (error, result) => {
+        if(error) {
+            logger.error('Unable to build package', package);
+            logger.error(error);
+            process.exit(1);
+        } else {
+            if(result && (typeof(result) === 'string') && result.startsWith('Unable')) {
+                logger.error(result);
+                process.exit(1);
+            }
+        }
+    });
 }
 
 const getName = () => {
     return 'Composum Package Manager';
 }
 
-function listPackages(url, username, password, path) {
+function listPackages(url, username, password, path, maxRetry) {
     var serviceURL = url + listEndpoint + path;
-    logger.debug('Service call: ', serviceURL);
-
-    let req = request.get({ url: serviceURL}, (error, response, body) => {
-        if (error) {
+    
+    let req = callGetService({serviceURL, username, password, maxRetry}, (error, json) => {
+        if(error) {
+            logger.error("Unable to list packages.");
             logger.error(error);
-        }
-
-        if (response && response.statusCode === 200) {
-            var json = JSON.parse(body);
-            
-            // Check for structure diff between Composume in Sling9 and Sling11
+            process.exit(1);
+        } else {
             var packages = json.children ? json.children : json;
             displayPackages(url, username, password, packages);
-        } else {
-            logger.error('Unable to connect to server. statusCode:', response && response.statusCode);
-            logger.debug(body);
         }
-
-    }).auth(username, password);
+    }); 
 
     logger.debug(JSON.stringify(req.toJSON()));
 }
@@ -206,6 +170,129 @@ function displayPackages(url, username, password, packages) {
     }
 }
 
+function postJob(data, operation, callback) {
+    data.serviceURL = data.url + '/bin/cpm/core/jobcontrol.job.json';
+    let post = callPostService(data, (error, json) => {
+        if(json && json['slingevent:eventId']) {
+            setTimeout(() => {
+                getJobOutput(data.url, data.username, data.password, json['slingevent:eventId'], callback)
+            },100);
+        } else {
+            callback(error, json); 
+        }
+    });
+
+    var form = post.form();
+    form.append('event.job.topic', 'com/composum/sling/core/pckgmgr/PackageJobExecutor');
+    form.append('_charset_', 'UTF-8');
+    form.append('operation', operation);
+    form.append('reference', data.package);
+
+    return post;
+}
+
+function callGetService(data, callback) {
+    data.method = "GET";
+    return callService(data, callback);
+}
+
+function callPostService(data, callback) {
+    data.method = "POST";
+    var post = callService(data, callback);
+    logger.debug('POST:', JSON.stringify(post.toJSON(), undefined, '   '));
+    return post;
+}
+
+function callService(data, callback) {
+    if(data.retryCount === undefined) {
+        data.retryCount = 0;
+        if(data.maxRetry === undefined) {
+            data.maxRetry = 10;
+        }
+    }
+
+    logger.debug(data.retryCount + '. Service call: ', data.serviceURL);
+
+    let req = request({ url: data.serviceURL, method: data.method }, (error, response, body) => {
+        var statusCodeLine = (response === undefined) ? "" : "Response: " + response.statusCode + " : " + response.statusMessage;
+        logger.debug(statusCodeLine);
+
+        if (error) {
+            if(data.retryCount < data.maxRetry) {
+                data.retryCount++;
+                callService(data, callback);
+            } else  { 
+                logger.error(error);
+                callback(error + " " + statusCodeLine, undefined); 
+            }
+
+            return;
+        }
+
+        if (response && response.statusCode === 200) {
+            if (body) {
+                var json = JSON.parse(body);
+                logger.debug('Response:', JSON.stringify(json, undefined, '   '));
+                callback(undefined, json);
+                return;
+            } else {
+                logger.debug("Respons has no body.");
+            }
+        }
+
+        if(data.retryCount < data.maxRetry) {
+            data.retryCount++;
+            callService(data, callback);
+        } else  { 
+            callback("Error calling service " + data.serviceURL, undefined);
+        }
+
+        return;
+    }).auth(data.username, data.password);
+    return req;
+}
+
+function getJobOutput(url, username, password, eventId, callback, jobState) {
+    var requestData = {url: url + "/bin/cpm/core/jobcontrol.outfile.txt/" + eventId};
+    if(jobState === undefined || jobState === "ACTIVE" || jobState === "QUEUED") {
+        requestData.url = url + "/bin/cpm/core/jobcontrol.job.json/" + eventId;
+    } 
+
+    request.get(requestData, (error, response, body) => {
+        var statusCodeLine = (response === undefined) ? "" : "Response: " + response.statusCode + " : " + response.statusMessage;
+        logger.debug(statusCodeLine);
+
+        if(error) {
+            logger.error(error);
+        } else if(body) {
+            if(body.trim().startsWith("{")) {
+                var json = JSON.parse(body);
+                logger.debug('Response:', JSON.stringify(json, undefined, '   '));
+                if(json["jobState"]) {
+                    setTimeout(()=>{
+                        getJobOutput(url, username, password, eventId, callback, json["jobState"]);
+                    }, 100);
+                    return;
+                }
+            } 
+            
+            logger.log(body.trim());
+        } else if (response && response.statusCode != 200) {
+            if(callback) {
+                callback('Package manager job service failed. '+statusCodeLine, undefined);
+            } else {
+                logger.error('Package manager job service failed.', statusCodeLine);
+                process.exit(1);
+            }
+        }
+
+        if(callback) {
+            callback(error, body); 
+        } 
+
+    }).auth(username, password);
+}
+
 module.exports = {
     checkService,
     list,
@@ -213,6 +300,7 @@ module.exports = {
     deletePackage,
     installPackage,
     uninstallPackage,
+    buildPackage,
     getName
 }