function changeCurRelease(cb) {
      console.log('Linking new release as current...');
      var curUpdateDir = path.join(config.path.releases, taskUid().get());
      seq(
        cmdFactory('mv ' + startUpdateDir + ' ' + curUpdateDir),
        cmdFactory('rm -R ' + config.path.app.replace(/\/$/, '') + ' && ln -s ' + curUpdateDir + ' ' + config.path.app.replace(/\/$/, '')),
        function(err, result) {
          if (err) return cb(err);

          needFallback = true;
          return cb(null);
        });
    }
Beispiel #2
0
        return function(next) {
          var runMigrations = false;
          var runNpmInstall = false;
          var runEditConfig = false;
          var runReindex = false;
          var runFXMigrations = false;

          seq(
            checkBranch,
            resetDir,
            gitFetch,
            checkMigrations,
            checkFXMigrations,
            checkPackageJson,
            checkConfigSample,
            checkIndexes,
            confirmUpdate,
            changeCurBranch,
            gitSrvUpdate,
            npmInstall,
            runMigrateTask,
            //resetDir,
            showResults,
            next
          );

          function checkBranch(cb) {
            if (argv.f) {
              shipit.log('> skipping branch test');
              return cb(null);
            }

            shipit.log('> Checking git repo:');
            shipit.remote('cd ' + target.path + ' && git rev-parse --abbrev-ref HEAD', function(err, res) {
              if (err) return cb(err);

              for (var i = 0; i < res.length; i++) {
                if (res[i].stdout.trim() !== target.branch) {
                  shipit.log('Current branch is "' + res[i].stdout.trim() + '", but configured "' + target.branch + '"');
                  return cb(new Error('branchCheckFailed'));
                }
              }
              return cb(null);
            });
          }

          function resetDir(cb) {
            shipit.log('> Reseting directory rights: ');
            var resetCmd;
            if (shipit.config.remote.resetDirScript) {
              resetCmd = shipit.config.remote.resetDirScript;
            } else {
              resetCmd = 'sudo /usr/bin/chown -R ' + target.owner + ':' + target.owner + ' ' +
                target.path + ' && sudo /usr/bin/chmod -R g+w ' + target.path;
            }

            shipit.remote(resetCmd, function(err, res) {
              if (err) return cb(err);

              shipit.log('%s: dir %s reset to "%s:%s"', shipit.config.servers, target.path, target.owner, target.owner);
              return cb(null);
            });
          }

          function gitFetch(cb) {
            shipit.log('> Fetching updates: ');
            shipit.remote('cd ' + target.path + ' && ' +
              ' git remote prune origin && git fetch', cb
            );
          }

          function checkMigrations(cb) {
            shipit.log('> Checking db migrations: ');
            var configName = 'config.json';
            var re = /(migrations\/.*\.(sql|js))/ig;

            shipit.mute();
            shipit.remote('cd ' + target.path +
              ' && git diff --name-only origin/' + target.branch,
              function(err, res) {
                shipit.unmute();

                if (err) return cb(err);

                if (!re.test(res[0].stdout)) {
                  shipit.log(chalk.dim('No migrations are found'));
                  return cb(null);
                }

                shipit.log(chalk.bgYellow('Migrations are found: ' + res[0].stdout.match(re).length + ' new'));
                runMigrations = true;

                var arr;
                var migrations = [];
                while ((arr = re.exec(res[0].stdout)) !== null) {
                  migrations.push(arr[0]);
                }

                for (var i = 0; i < migrations.length; i++) {
                  shipit.log('%s. %s', i + 1, migrations[i]);
                }

                shipit.remote('cd ' + target.path +
                  ' && cat ./' + configName,
                  function(err, res) {
                    if (err) {
                      shipit.log('config not found');
                      return cb(null);
                    }

                    shipit.log('Parsing config: ');
                    var config;
                    try {
                      config = JSON.parse(res[0].stdout);
                    } catch (e) {
                      shipit.log(e);
                      return cb('Can\'t parse the ' + configName);
                    }

                    var cmd = 'mysql -h' + config.mysql.host + ' -u' + config.mysql.user + ' -p\'' + config.mysql.password + '\' ' + config.mysql.database;
                    shipit.log('You should run: ');
                    for (var i = 0; i < migrations.length; i++) {
                      shipit.log(cmd + ' < ' + migrations[i]);
                    }

                    return cb(null);
                  });
              });
          }

          function checkFXMigrations(cb) {
            shipit.log('> Checking fx migrations: ');
            var re = /(migrations\/flexible\/.*\.xml)/ig;

            shipit.mute();
            shipit.remote('cd ' + target.path +
              ' && git diff --name-only origin/' + target.branch,
              function(err, res) {
                shipit.unmute();

                if (err) return cb(err);

                if (!re.test(res[0].stdout)) {
                  shipit.log(chalk.dim('No fx migrations are found'));
                  return cb(null);
                }

                shipit.log(chalk.bgYellow('FX migrations are found: ' + res[0].stdout.match(re).length + ' new'));
                runFXMigrations = true;

                var arr;
                var migrations = [];
                while ((arr = re.exec(res[0].stdout)) !== null) {
                  migrations.push(arr[0]);
                }

                for (var i = 0; i < migrations.length; i++) {
                  shipit.log('%s. %s', i + 1, migrations[i]);
                }


                cb(null);
              });
          }

          function checkPackageJson(cb) {
            shipit.log('> Checking package.json changes: ');
            shipit.remote('cd ' + target.path +
              ' && git diff ..origin/' + target.branch + ' package.json | grep \'^+\'' +
              ' | awk \'!/package.json/\' | awk \'!/version/\'',
              function(err, res) {
                if (err) return cb(err);

                if (!res[0].stdout) {
                  shipit.log(chalk.dim('No new packages are found'));
                  return cb(null);
                }

                shipit.log(chalk.bgYellow('New packages are found!'));
                shipit.log('You should run "npm install" to install them!');
                runNpmInstall = true;
                return cb(null);
              });
          }

          function checkConfigSample(cb) {
            shipit.log('> Checking config file changes...');
            var configRe = /(config.(js|json)\.sample)/ig;
            shipit.mute();
            shipit.remote('cd ' + target.path + ' && ls', function(err, res) {
              shipit.unmute();

              if (err) return cb(err);

              if (!configRe.test(res[0].stdout)) {
                shipit.log('no config sample is found..');
                return cb(null);
              }

              var configName = res[0].stdout.match(configRe)[0];

              shipit.remote('cd ' + target.path +
                ' && git diff ..origin/' + target.branch + ' ' + configName,
                function(err, res) {
                  if (err) return cb(err);

                  if (!res[0].stdout) {
                    shipit.log(chalk.dim('No config changes are found'));
                    return cb(null);
                  }

                  // for easy copy/paste
                  console.log(res[0].stdout);

                  shipit.log(chalk.bgYellow('Config changes are found!'));
                  shipit.log('You should edit the config file!');
                  runEditConfig = true;

                  return cb(null);
                });
            });
          }

          function checkIndexes(cb) {
            shipit.log('> Checking changes in indexes.js...');
            var fnameRe = /(indexes.js)/ig;
            shipit.mute();
            shipit.remote('cd ' + target.path + ' && ls', function(err, res) {
              shipit.unmute();

              if (err) return cb(err);

              if (!fnameRe.test(res[0].stdout)) {
                shipit.log('indexes.js not found');
                return cb(null);
              }

              var fileName = res[0].stdout.match(fnameRe)[0];

              shipit.remote('cd ' + target.path +
                ' && git diff ..origin/' + target.branch + ' ' + fileName,
                function(err, res) {
                  if (err) return cb(err);

                  if (!res[0].stdout) {
                    shipit.log(chalk.dim('indexes.js changes not found'));
                    return cb(null);
                  }

                  shipit.log(chalk.bgYellow('indexes.js changes are found!'));
                  shipit.log('You should rebuild sphinx indexes');
                  runReindex = true;

                  return cb(null);
                });
            });
          }

          function confirmUpdate(cb) {
            shipit.log('> Files to be updated:');
            shipit.remote('cd ' + target.path +
              ' && git diff --color --name-status ..origin/' + target.branch,
              function(err, res) {
                if (err) return cb(err);

                if (runMigrations) {
                  if (target.migrateTask) {
                    shipit.log(chalk.bgRed('You should run task `migrate`!'));
                  } else {
                    shipit.log(chalk.bgRed('You should run the migrations!'));
                  }
                }

                if (runEditConfig) shipit.log(chalk.bgRed('You should edit the config file!'));
                if (runReindex) shipit.log(chalk.bgRed('You should rebuild Sphinx indexes'));
                if (runFXMigrations) shipit.log(chalk.bgRed('You should run task `migrate_flexible`!'));

                inquirer.prompt([{
                  type: 'confirm',
                  message: 'Do you want to update the ' + chalk.yellow(targetName) + ' ?',
                  name: 'confirm'
                }], function(ans) {
                  if (!ans.confirm) {
                    shipit.log('task was stopped by user');
                    return taskCallback();
                  }
                  return cb(null);
                });
              });
          }

          function changeCurBranch(cb) {
            if (!argv.f) return cb(null);

            shipit.log('> Changing current git branch: ');
            shipit.remote('cd ' + target.path +
              ' && git checkout ' + target.branch, cb);
          }

          function gitSrvUpdate(cb) {
            shipit.log('> Updating servers: ');
            shipit.remote('cd ' + target.path +
              ' && git reset --hard origin/' + target.branch,
              function(err, res) {
                if (err) return cb(err);

                shipit.log(chalk.green('The ') + chalk.yellow(targetName) + chalk.green(' is reset to "origin/' + target.branch + '"'));
                return cb(null);
              });
          }

          function npmInstall(cb) {
            if (runNpmInstall) shipit.log(chalk.red('New modules were found, you should run "npm intall"!'));

            inquirer.prompt([{
              type: 'confirm',
              message: 'Do you want to run npm install?',
              name: 'confirm',
              default: runNpmInstall
            }], function(ans) {
              if (!ans.confirm) return cb(null);
              shipit.remote('cd ' + target.path + ' && npm install', cb);
            });
          }

          function runMigrateTask(cb) {
            if (!(runMigrations && target.migrateTask)) return cb(null);

            var env = target.branch === 'master' ? 'production' : 'development';
            var ndPath = targets.hasOwnProperty('noodoo') ?
              targets.noodoo.path : path.join(target.path, '..', '..');
            var cmd = 'cd ' + ndPath + ' && NODE_ENV=' + env +
              ' node tasks ' + path.basename(target.path) + '/migrate';

            shipit.log('You should run: ');
            shipit.log(chalk.yellow(cmd));

            inquirer.prompt([{
              type: 'confirm',
              message: 'Do you want to run migrate task?',
              name: 'confirm',
              default: false
            }], function(ans) {
              if (!ans.confirm) return cb(null);
              shipit.remoteSingle(cmd, cb);
            });
          }

          function showResults(cb) {
            shipit.log('> Results: ');
            shipit.remote('cd ' + target.path +
              ' && echo "Git log:" && git log --color --oneline -n5' +
              ' && echo "\nLast changed files:" && git diff --color --name-status @{1}.. || true', cb);
          }
        };
Beispiel #3
0
    function promptSelTasks(cb) {
      var prompts = [];
      var choices;
      var config = shipit.config.remote;

      // get remote dirs for the recursive tasks first
      var tasks = {};
      for (var id in config.targets) {
        if (config.targets[id].recursive) {
          tasks[id] = remoteDirsFac(config.targets[id].path);
        }
      }

      function remoteDirsFac(remotePath) {
        return function(cb) {
          lib.getRemoteDirs(remotePath, function(err, childDirs) {
            if (err) return cb(err);
            return cb(null, childDirs);
          });
        };
      }

      seq(tasks, function(err, childDirs) {
        if (err) return cb(err);
        selTasks(childDirs);
      });

      function selTasks(childDirs) {
        choices = [];
        for (var id in config.targets) {
          if (config.targets[id].recursive) {
            for (var i = 0; i < childDirs[id].length; i++) {
              choices.push({
                name: id + '/' + childDirs[id][i],
                checked: false
              });
            }
          } else {
            choices.push({
              name: id,
              checked: false
            });
          }
          choices.push(new inquirer.Separator());
        }
        choices.pop(); // last separator

        prompts.push({
          type: 'checkbox',
          message: 'Select target to update: ',
          name: 'tasks',
          choices: choices,
        });

        inquirer.prompt(prompts, function(res) {
          var selTasks = {};
          var count = 0;
          var rtaskId, dirname;

          for (var taskId in res) {
            if (!res.hasOwnProperty(taskId)) continue;

            for (var i = 0; i < res[taskId].length; i++) {
              count++;
              if (config.targets[res[taskId][i]]) {
                selTasks[res[taskId][i]] = config.targets[res[taskId][i]];
              } else {
                rtaskId = res[taskId][i].split('/')[0];
                dirname = res[taskId][i].split('/')[1];
                selTasks[res[taskId][i]] = utils.copy(config.targets[rtaskId]);
                selTasks[res[taskId][i]].path = path.join(selTasks[res[taskId][i]].path, dirname);
              }
            }
          }

          if (count === 0) return cb('You must choose at least one task to continue');

          return cb(null, selTasks);
        });
      }
    }
Beispiel #4
0
    function doTasks(targets) {

      seq(
        // target's unique
        targetLoop,

        // server's general
        confirmRestart,
        runRestartScript,
        showStatus,
        dovReport,
        taskCallback
      );

      function slog(text) {
        var args = Array.prototype.slice.call(arguments, 1);
        args.unshift(chalk.yellow(text));

        return function(next) {
          console.log.apply(console, args);
          next();
        };
      }

      function targetLoop(cb) {
        var tasks = [];
        for (var key in targets) {
          tasks.push(slog('Synchronizing ' + key + '...'));
          tasks.push(targetFactory(targets[key], key));
          tasks.push(slog('The ' + key + ' is synchronized with origin!'));
        }
        tasks.push(cb);

        seq.apply(seq, tasks);
      }

      function targetFactory(target, targetName) {
        return function(next) {
          var runMigrations = false;
          var runNpmInstall = false;
          var runEditConfig = false;
          var runReindex = false;
          var runFXMigrations = false;

          seq(
            checkBranch,
            resetDir,
            gitFetch,
            checkMigrations,
            checkFXMigrations,
            checkPackageJson,
            checkConfigSample,
            checkIndexes,
            confirmUpdate,
            changeCurBranch,
            gitSrvUpdate,
            npmInstall,
            runMigrateTask,
            //resetDir,
            showResults,
            next
          );

          function checkBranch(cb) {
            if (argv.f) {
              shipit.log('> skipping branch test');
              return cb(null);
            }

            shipit.log('> Checking git repo:');
            shipit.remote('cd ' + target.path + ' && git rev-parse --abbrev-ref HEAD', function(err, res) {
              if (err) return cb(err);

              for (var i = 0; i < res.length; i++) {
                if (res[i].stdout.trim() !== target.branch) {
                  shipit.log('Current branch is "' + res[i].stdout.trim() + '", but configured "' + target.branch + '"');
                  return cb(new Error('branchCheckFailed'));
                }
              }
              return cb(null);
            });
          }

          function resetDir(cb) {
            shipit.log('> Reseting directory rights: ');
            var resetCmd;
            if (shipit.config.remote.resetDirScript) {
              resetCmd = shipit.config.remote.resetDirScript;
            } else {
              resetCmd = 'sudo /usr/bin/chown -R ' + target.owner + ':' + target.owner + ' ' +
                target.path + ' && sudo /usr/bin/chmod -R g+w ' + target.path;
            }

            shipit.remote(resetCmd, function(err, res) {
              if (err) return cb(err);

              shipit.log('%s: dir %s reset to "%s:%s"', shipit.config.servers, target.path, target.owner, target.owner);
              return cb(null);
            });
          }

          function gitFetch(cb) {
            shipit.log('> Fetching updates: ');
            shipit.remote('cd ' + target.path + ' && ' +
              ' git remote prune origin && git fetch', cb
            );
          }

          function checkMigrations(cb) {
            shipit.log('> Checking db migrations: ');
            var configName = 'config.json';
            var re = /(migrations\/.*\.(sql|js))/ig;

            shipit.mute();
            shipit.remote('cd ' + target.path +
              ' && git diff --name-only origin/' + target.branch,
              function(err, res) {
                shipit.unmute();

                if (err) return cb(err);

                if (!re.test(res[0].stdout)) {
                  shipit.log(chalk.dim('No migrations are found'));
                  return cb(null);
                }

                shipit.log(chalk.bgYellow('Migrations are found: ' + res[0].stdout.match(re).length + ' new'));
                runMigrations = true;

                var arr;
                var migrations = [];
                while ((arr = re.exec(res[0].stdout)) !== null) {
                  migrations.push(arr[0]);
                }

                for (var i = 0; i < migrations.length; i++) {
                  shipit.log('%s. %s', i + 1, migrations[i]);
                }

                shipit.remote('cd ' + target.path +
                  ' && cat ./' + configName,
                  function(err, res) {
                    if (err) {
                      shipit.log('config not found');
                      return cb(null);
                    }

                    shipit.log('Parsing config: ');
                    var config;
                    try {
                      config = JSON.parse(res[0].stdout);
                    } catch (e) {
                      shipit.log(e);
                      return cb('Can\'t parse the ' + configName);
                    }

                    var cmd = 'mysql -h' + config.mysql.host + ' -u' + config.mysql.user + ' -p\'' + config.mysql.password + '\' ' + config.mysql.database;
                    shipit.log('You should run: ');
                    for (var i = 0; i < migrations.length; i++) {
                      shipit.log(cmd + ' < ' + migrations[i]);
                    }

                    return cb(null);
                  });
              });
          }

          function checkFXMigrations(cb) {
            shipit.log('> Checking fx migrations: ');
            var re = /(migrations\/flexible\/.*\.xml)/ig;

            shipit.mute();
            shipit.remote('cd ' + target.path +
              ' && git diff --name-only origin/' + target.branch,
              function(err, res) {
                shipit.unmute();

                if (err) return cb(err);

                if (!re.test(res[0].stdout)) {
                  shipit.log(chalk.dim('No fx migrations are found'));
                  return cb(null);
                }

                shipit.log(chalk.bgYellow('FX migrations are found: ' + res[0].stdout.match(re).length + ' new'));
                runFXMigrations = true;

                var arr;
                var migrations = [];
                while ((arr = re.exec(res[0].stdout)) !== null) {
                  migrations.push(arr[0]);
                }

                for (var i = 0; i < migrations.length; i++) {
                  shipit.log('%s. %s', i + 1, migrations[i]);
                }


                cb(null);
              });
          }

          function checkPackageJson(cb) {
            shipit.log('> Checking package.json changes: ');
            shipit.remote('cd ' + target.path +
              ' && git diff ..origin/' + target.branch + ' package.json | grep \'^+\'' +
              ' | awk \'!/package.json/\' | awk \'!/version/\'',
              function(err, res) {
                if (err) return cb(err);

                if (!res[0].stdout) {
                  shipit.log(chalk.dim('No new packages are found'));
                  return cb(null);
                }

                shipit.log(chalk.bgYellow('New packages are found!'));
                shipit.log('You should run "npm install" to install them!');
                runNpmInstall = true;
                return cb(null);
              });
          }

          function checkConfigSample(cb) {
            shipit.log('> Checking config file changes...');
            var configRe = /(config.(js|json)\.sample)/ig;
            shipit.mute();
            shipit.remote('cd ' + target.path + ' && ls', function(err, res) {
              shipit.unmute();

              if (err) return cb(err);

              if (!configRe.test(res[0].stdout)) {
                shipit.log('no config sample is found..');
                return cb(null);
              }

              var configName = res[0].stdout.match(configRe)[0];

              shipit.remote('cd ' + target.path +
                ' && git diff ..origin/' + target.branch + ' ' + configName,
                function(err, res) {
                  if (err) return cb(err);

                  if (!res[0].stdout) {
                    shipit.log(chalk.dim('No config changes are found'));
                    return cb(null);
                  }

                  // for easy copy/paste
                  console.log(res[0].stdout);

                  shipit.log(chalk.bgYellow('Config changes are found!'));
                  shipit.log('You should edit the config file!');
                  runEditConfig = true;

                  return cb(null);
                });
            });
          }

          function checkIndexes(cb) {
            shipit.log('> Checking changes in indexes.js...');
            var fnameRe = /(indexes.js)/ig;
            shipit.mute();
            shipit.remote('cd ' + target.path + ' && ls', function(err, res) {
              shipit.unmute();

              if (err) return cb(err);

              if (!fnameRe.test(res[0].stdout)) {
                shipit.log('indexes.js not found');
                return cb(null);
              }

              var fileName = res[0].stdout.match(fnameRe)[0];

              shipit.remote('cd ' + target.path +
                ' && git diff ..origin/' + target.branch + ' ' + fileName,
                function(err, res) {
                  if (err) return cb(err);

                  if (!res[0].stdout) {
                    shipit.log(chalk.dim('indexes.js changes not found'));
                    return cb(null);
                  }

                  shipit.log(chalk.bgYellow('indexes.js changes are found!'));
                  shipit.log('You should rebuild sphinx indexes');
                  runReindex = true;

                  return cb(null);
                });
            });
          }

          function confirmUpdate(cb) {
            shipit.log('> Files to be updated:');
            shipit.remote('cd ' + target.path +
              ' && git diff --color --name-status ..origin/' + target.branch,
              function(err, res) {
                if (err) return cb(err);

                if (runMigrations) {
                  if (target.migrateTask) {
                    shipit.log(chalk.bgRed('You should run task `migrate`!'));
                  } else {
                    shipit.log(chalk.bgRed('You should run the migrations!'));
                  }
                }

                if (runEditConfig) shipit.log(chalk.bgRed('You should edit the config file!'));
                if (runReindex) shipit.log(chalk.bgRed('You should rebuild Sphinx indexes'));
                if (runFXMigrations) shipit.log(chalk.bgRed('You should run task `migrate_flexible`!'));

                inquirer.prompt([{
                  type: 'confirm',
                  message: 'Do you want to update the ' + chalk.yellow(targetName) + ' ?',
                  name: 'confirm'
                }], function(ans) {
                  if (!ans.confirm) {
                    shipit.log('task was stopped by user');
                    return taskCallback();
                  }
                  return cb(null);
                });
              });
          }

          function changeCurBranch(cb) {
            if (!argv.f) return cb(null);

            shipit.log('> Changing current git branch: ');
            shipit.remote('cd ' + target.path +
              ' && git checkout ' + target.branch, cb);
          }

          function gitSrvUpdate(cb) {
            shipit.log('> Updating servers: ');
            shipit.remote('cd ' + target.path +
              ' && git reset --hard origin/' + target.branch,
              function(err, res) {
                if (err) return cb(err);

                shipit.log(chalk.green('The ') + chalk.yellow(targetName) + chalk.green(' is reset to "origin/' + target.branch + '"'));
                return cb(null);
              });
          }

          function npmInstall(cb) {
            if (runNpmInstall) shipit.log(chalk.red('New modules were found, you should run "npm intall"!'));

            inquirer.prompt([{
              type: 'confirm',
              message: 'Do you want to run npm install?',
              name: 'confirm',
              default: runNpmInstall
            }], function(ans) {
              if (!ans.confirm) return cb(null);
              shipit.remote('cd ' + target.path + ' && npm install', cb);
            });
          }

          function runMigrateTask(cb) {
            if (!(runMigrations && target.migrateTask)) return cb(null);

            var env = target.branch === 'master' ? 'production' : 'development';
            var ndPath = targets.hasOwnProperty('noodoo') ?
              targets.noodoo.path : path.join(target.path, '..', '..');
            var cmd = 'cd ' + ndPath + ' && NODE_ENV=' + env +
              ' node tasks ' + path.basename(target.path) + '/migrate';

            shipit.log('You should run: ');
            shipit.log(chalk.yellow(cmd));

            inquirer.prompt([{
              type: 'confirm',
              message: 'Do you want to run migrate task?',
              name: 'confirm',
              default: false
            }], function(ans) {
              if (!ans.confirm) return cb(null);
              shipit.remoteSingle(cmd, cb);
            });
          }

          function showResults(cb) {
            shipit.log('> Results: ');
            shipit.remote('cd ' + target.path +
              ' && echo "Git log:" && git log --color --oneline -n5' +
              ' && echo "\nLast changed files:" && git diff --color --name-status @{1}.. || true', cb);
          }
        };
      }

      function confirmRestart(cb) {
        inquirer.prompt([{
          type: 'confirm',
          message: 'Do you want to restart the servers?',
          name: 'confirm'
        }], function(ans) {
          if (!ans.confirm) {
            shipit.log('task was stopped by user');

            return dovReport(taskCallback);
          }
          return cb(null);
        });
      }

      function runRestartScript(cb) {
        shipit.log('> Restarting servers: ');

        var openUrls = [];
        for (var key in targets) {
          if (targets[key].openUrl) openUrls.push(targets[key].openUrl);
        }

        shipit.remoteOneByOne(shipit.config.remote.restartScript, {
            openUrls: openUrls
          },
          function(err, res) {
            if (err) return cb(err);

            shipit.log(chalk.green('All nodes from "%s" are restarted'), shipit.config.servers);

            // for (var key in targets) {
            //   console.log(targets[key]);
            //   if (targets[key].openUrl) open(targets[key].openUrl);
            // }
            return cb(null);
          });
      }

      function showStatus(cb) {
        shipit.log('> Server status: ');
        setTimeout(function() {
          shipit.remote(shipit.config.remote.showStatusScript, cb);
        }, 2000);
      }

      function dovReport(cb) {
        var tasks = [];
        for (var key in targets) {
          tasks.push(reportFactory(targets[key], key));
        }
        tasks.push(cb);
        seq.apply(seq, tasks);

        function reportFactory(target, targetName) {
          return function(next) {
            shipit.log('> Checking release');
            var tagRe = /v(\d+.\d+.\d+)$/i;
            shipit.remote('cd ' + target.path + ' && git describe --tags', function(err, res) {
              if (err) return next(null);

              if (!tagRe.test(res[0].stdout.trim())) return next(null);

              shipit.log('release is found!');
              var tag = res[0].stdout.trim().match(tagRe)[1];

              shipit.mute();
              shipit.remote('cd ' + target.path + ' && cat ./CHANGELOG.md', function(err, res) {
                shipit.unmute();

                if (err) return next(err);

                var data = res[0].stdout.trim();
                var lines = data.split('\n');
                var block = [];
                var count = 0;

                for (var i = 0; i < lines.length; i++) {
                  if (lines[i].indexOf('##') === 0) count++;
                  if (count === 2) block.push(lines[i]);
                  if (count === 3) break;
                }
                block.splice(0, 1);

                console.log('<b>Версия ' + tag + ' для ' + targetName + ' собрана и установлена на PROD.</b>');
                console.log('Список изменений:');
                block.forEach(function(line) {
                  if (line && line.trim() !== '') console.log(line);
                });
                return next(null);
              });
            });
          };
        }
      }
    }
  shipit.task('build', function(taskCallback) {
    require('./init')(shipit);

    argv = require('yargs')
      .usage('Usage: $0 build <release-version>')
      .example('$0 build -v 1.6.2')
      .describe('v', 'Specify build version in semver format: X.Y.Z')
      .alias('v', 'version')
      .alias('p', ['patch'])
      .describe('p', 'Build patch version: X.Y.Z+1')
      .alias('n', 'minor')
      .describe('n', 'Build minor version: X.Y+1.0')
      .demand(2, 'No release version specified')
      .help('h')
      .alias('h', 'help')
      .argv;

    seq(
      gitReset,
      gitUpdate,
      parseVersion,
      updatePackageJson,
      updateChangelog,
      commit,
      addTag,
      confirm,
      gitPush,
      gitPushTags,
      printDovReport,
      copy2clipboard,
      taskCallback
    );

    function gitReset(branch, cb) {
      shipit.log('> Reseting branch "%s"', shipit.config.branch);
      shipit.local('git reset --hard origin/' + shipit.config.branch, {
        cwd: shipit.config.local.path
      }, cb);
    }

    function gitUpdate(cb) {
      shipit.log('> Fetching updates:');
      shipit.local('git checkout ' + shipit.config.branch + ' && git fetch && git diff --name-status origin/' + shipit.config.branch + ' && git reset --hard origin/' + shipit.config.branch, {
        cwd: shipit.config.local.path
      }, function(err, res) {
        if (err) return cb(err);
        shipit.log(chalk.green('"%s" is reset to "origin/%s"'), shipit.config.branch, shipit.config.branch);
        return cb(null);
      });
    }

    function parseVersion(cb) {
      if (argv.v) return parse(argv.v);

      shipit.local('git describe --abbrev=0 --tags', {
        cwd: shipit.config.local.path
      }, function(err, res) {
        if (err) return cb(err);
        parse(res.stdout);
      });

      function parse(versionString) {
        var s = versionString.trim();

        ver.x = parseInt(s.match(reVer)[1], 10);
        ver.y = parseInt(s.match(reVer)[2], 10);
        ver.z = parseInt(s.match(reVer)[3], 10);

        if (argv.p) ver.z++;
        if (argv.n) {
          ver.y++;
          ver.z = 0;
        }

        shipit.log('> Release version: %s.%s.%s', ver.x, ver.y, ver.z);
        return cb(null);
      }
    }

    function updatePackageJson(cb) {
      shipit.log('> Edit package.json');
      var file = path.join(shipit.config.local.path, 'package.json');
      var content = fs.readFileSync(file, 'utf8');
      content = content.replace(
        /"version":.*"(\d+.\d+.\d+)"/i,
        '"version": "' + ver.toString() + '"'
      );
      fs.writeFileSync(file, content, 'utf8');
      return cb(null);
    }

    function updateChangelog(cb) {
      shipit.log('> Edit CHANGELOG.md');
      var file = path.join(shipit.config.local.path, 'CHANGELOG.md');
      var data = fs.readFileSync(file).toString();
      var lines = data.split('\n');
      var insertlineNumber = 1;

      var urlRe = /(^.*https:\/\/github.com\/.*\/compare\/)/im;
      var url = data.match(urlRe);
      if (url) {
        console.log('> inserting compare url...');
        url = url[0];
        var prev = 'v' + data.match(/##\s(\d+\.\d+\.\d+)/i)[1];
        url = url + prev + '...' + 'v' + ver.toString();
      }

      var ts = moment().format('YYYY-MM-DD HH:mm');
      var insertion = '\n## ' + ver.toString() + ' ' + ts;
      if (url) insertion += '\n\n' + url;

      lines.splice(insertlineNumber, 0, insertion);

      var text = lines.join('\n');

      fs.writeFile(file, text, function(err) {
        if (err) return cb(err);
        return cb(null);
      });
    }

    function commit(cb) {
      var m = 'Version ' + ver.toString();
      shipit.log('> Creating commit "%s"', m);
      shipit.local('git commit -am "' + m + '"', {
        cwd: shipit.config.local.path
      }, function(err, res) {
        if (err) return cb(err);

        shipit.log(chalk.green('Commit added'));
        return cb(null);
      });
    }

    function addTag(cb) {
      var tag = 'v' + ver.toString();
      shipit.log('> Adding new tag "%s"', tag);
      shipit.local('git tag ' + tag, {
        cwd: shipit.config.local.path
      }, function(err, res) {
        if (err) return cb(err);

        shipit.log(chalk.green('Tag added'));
        return cb(null);
      });
    }

    function removeTag(cb) {
      var tag = 'v' + ver.toString();
      shipit.log('> Removing new tag locally "%s"', tag);
      shipit.local('git tag -d ' + tag, {
        cwd: shipit.config.local.path
      }, function(err, res) {
        if (err) return cb(err);

        shipit.log(chalk.green('Tag removed'));
        return cb(null);
      });
    }

    function confirm(cb) {
      shipit.local('head -n20 CHANGELOG.md && echo "++++" && head package.json && git hist -n10', {
        cwd: shipit.config.local.path
      }, function(err, res) {
        if (err) return cb(err);
        inquirer.prompt([{
          type: 'confirm',
          message: 'Push "' + shipit.config.branch + '" to "origin/' + shipit.config.branch + '"?',
          name: 'confirm'
        }], function(ans) {
          if (!ans.confirm) {
            shipit.log('task was stopped by user');
            return removeTag(taskCallback);
          }
          return cb(null);
        });
      });
    }

    function gitPush(cb) {
      shipit.log('> Pushing new version to origin: ');
      shipit.local('git push origin ' + shipit.config.branch + ':' + shipit.config.branch, {
        cwd: shipit.config.local.path
      }, function(err, res) {
        if (err) return cb(err);

        shipit.log(chalk.green('"%s" is pushed to "origin/%s"'), shipit.config.branch, shipit.config.branch);
        return cb(null);
      });
    }

    function gitPushTags(cb) {
      shipit.log('> Pushing tags to origin: ');
      shipit.local('git push origin --tags', {
        cwd: shipit.config.local.path
      }, function(err, res) {
        if (err) return cb(err);

        shipit.log(chalk.green('all tags are pushed to origin'));
        return cb(null);
      });
    }

    function printDovReport(cb) {
      dovReport = '<b>Собран релиз ' + ver.toString() + ' для ' +
        path.basename(shipit.config.local.path) + ' и установлен на PROD.</b>\n';

      var file = path.join(shipit.config.local.path, 'CHANGELOG.md');
      var block = [];
      var count = 0;
      //var reTag = /^##/;

      var readline = require('readline');
      var fs = require('fs');
      var fstream = fs.createReadStream(file);

      var rl = readline.createInterface({
        input: fstream
      });

      rl.on('line', function(line) {
        // console.log(line);
        // there is no documented way to close/shutdown/abort/destroy
        // a generic Readable stream as of Node 5.3.0.
        if (count > 2) return;

        if (line.indexOf('##') !== -1) count++;
        // save all between second and third "##""
        if (count === 2) block.push(line);
      }).on('close', done);

      function done() {
        block.splice(0, 1);
        dovReport += 'Список изменений:\n';
        //console.log('<ul>');
        block.forEach(function(line) {
          if (line && line.trim().length > 0) {
            console.log(line);
            dovReport += line + '\n';
          }
        });

        // console.log(dovReport);
        cb(null);
      }
    }

    function copy2clipboard(cb) {
      shipit.local('echo "' + dovReport + '" | pbcopy', 'copying to clipboard..', cb);
    }
  });
    function checkChangelog(branch, cb) {
      // all changes should be in one single section between ##master and ## x.y.z

      seq(
        checkSingleSection,
        checkPlacement,
        checkDuplicates,
        cb
      );

      function checkSingleSection(branch, next) {
        shipit.mute();
        shipit.local('git diff -U0 origin/master CHANGELOG.md | grep "@@" | wc -l', {
          cwd: shipit.config.local.path
        }, function(err, res) {
          shipit.unmute();

          if (err) return next(err);

          if (res.stdout.trim() !== '1') isOK = false;

          return next(null);
        });
      }

      function checkPlacement(next) {
        shipit.log('Running CHANGELOG checks...');
        var reMaster = /##\smaster/i;
        var reVer = /##\s(\d+)\.(\d+)\.(\d+)/;
        var reDiff = /^[+-]\s/;

        shipit.mute();
        shipit.local('git diff -U10 origin/master CHANGELOG.md', {
          cwd: shipit.config.local.path
        }, function(err, res) {
          shipit.unmute();

          if (err) return next(err);

          if (res.stdout.trim() === '') {
            isOK = true;
            return next(null);
          }

          var lines = res.stdout.split('\n');
          var masterFound, afterMasterSection;
          for (var i = 0; i < lines.length; i++) {
            if (reMaster.test(lines[i])) masterFound = true;
            if (masterFound && reVer.test(lines[i])) afterMasterSection = true;
            if (afterMasterSection) {
              //console.log(lines[i]);
              if (reDiff.test(lines[i].trim())) {
                isOK = false;
                break;
              }
            }
          }

          return next(null);
        });
      }

      function checkDuplicates(next) {
        //var cmd = "/bin/bash -c \"git diff -U0 origin/master CHANGELOG.md | grep -v '^[\s+]$' | sort | uniq -c | grep -v '^ *1 ' | grep -v '\[Post\]'\"";
        var rePost = /\[Post\]/;

        shipit.mute();
        shipit.local('git diff origin/master CHANGELOG.md', {
          cwd: shipit.config.local.path
        }, function(err, res) {
          shipit.unmute();

          if (err) return next(err);

          var lines = res.stdout.split('\n');
          var sorted_arr = lines.slice().sort();
          for (var i = 0; i < sorted_arr.length - 1; i++) {
            if (sorted_arr[i].length > 10 && !rePost.test(sorted_arr[i]) &&
              sorted_arr[i + 1] == sorted_arr[i]) {
              dups.push(sorted_arr[i]);
            }
          }

          if (dups.length > 0) isOK = false;

          return next(null);
        });
      }
    }
  shipit.task('bupd', function(taskCallback) {
    require('./init')(shipit);

    argv = require('yargs')
      .usage('Usage: $0 bupd -b <branch-name>')
      .example('$0 build -b test')
      .alias('b', ['branch'])
      .describe('b', 'Update branch')
      .help('h')
      .alias('h', 'help')
      .argv;

    var branches = ['test', 'staging', 'development'];
    if (argv.branch) branches = [argv.branch];

    var isOK, isPush, dups = [];

    seq(
      branchLoop,
      taskCallback
    );

    function branchLoop(cb) {
      var tasks = [];
      branches.forEach(function(branch) {
        tasks = tasks.concat([
          seqFactory(gitReset, branch),
          seqFactory(gitMergeMaster, branch),
          seqFactory(checkChangelog, branch),
          seqFactory(confirm, branch),
          seqFactory(gitPush, branch)
        ]);
      });

      tasks.push(cb);

      seq.apply(seq, tasks);

      function seqFactory(func, branch) {
        return function(next) {
          return func(branch, next);
        };
      }
    }

    function gitReset(branch, cb) {
      isOK = true;
      shipit.log('> Reseting branch "%s"', branch);
      shipit.local('git checkout ' + branch +
        ' && git fetch && git reset --hard origin/' + branch, {
          cwd: shipit.config.local.path
        }, cb);
    }

    function gitMergeMaster(branch, cb) {
      shipit.log('> Merging "master" into "%s"', branch);
      shipit.local('git pull origin master', {
        cwd: shipit.config.local.path
      }, cb);
    }

    function checkChangelog(branch, cb) {
      // all changes should be in one single section between ##master and ## x.y.z

      seq(
        checkSingleSection,
        checkPlacement,
        checkDuplicates,
        cb
      );

      function checkSingleSection(branch, next) {
        shipit.mute();
        shipit.local('git diff -U0 origin/master CHANGELOG.md | grep "@@" | wc -l', {
          cwd: shipit.config.local.path
        }, function(err, res) {
          shipit.unmute();

          if (err) return next(err);

          if (res.stdout.trim() !== '1') isOK = false;

          return next(null);
        });
      }

      function checkPlacement(next) {
        shipit.log('Running CHANGELOG checks...');
        var reMaster = /##\smaster/i;
        var reVer = /##\s(\d+)\.(\d+)\.(\d+)/;
        var reDiff = /^[+-]\s/;

        shipit.mute();
        shipit.local('git diff -U10 origin/master CHANGELOG.md', {
          cwd: shipit.config.local.path
        }, function(err, res) {
          shipit.unmute();

          if (err) return next(err);

          if (res.stdout.trim() === '') {
            isOK = true;
            return next(null);
          }

          var lines = res.stdout.split('\n');
          var masterFound, afterMasterSection;
          for (var i = 0; i < lines.length; i++) {
            if (reMaster.test(lines[i])) masterFound = true;
            if (masterFound && reVer.test(lines[i])) afterMasterSection = true;
            if (afterMasterSection) {
              //console.log(lines[i]);
              if (reDiff.test(lines[i].trim())) {
                isOK = false;
                break;
              }
            }
          }

          return next(null);
        });
      }

      function checkDuplicates(next) {
        //var cmd = "/bin/bash -c \"git diff -U0 origin/master CHANGELOG.md | grep -v '^[\s+]$' | sort | uniq -c | grep -v '^ *1 ' | grep -v '\[Post\]'\"";
        var rePost = /\[Post\]/;

        shipit.mute();
        shipit.local('git diff origin/master CHANGELOG.md', {
          cwd: shipit.config.local.path
        }, function(err, res) {
          shipit.unmute();

          if (err) return next(err);

          var lines = res.stdout.split('\n');
          var sorted_arr = lines.slice().sort();
          for (var i = 0; i < sorted_arr.length - 1; i++) {
            if (sorted_arr[i].length > 10 && !rePost.test(sorted_arr[i]) &&
              sorted_arr[i + 1] == sorted_arr[i]) {
              dups.push(sorted_arr[i]);
            }
          }

          if (dups.length > 0) isOK = false;

          return next(null);
        });
      }
    }

    function confirm(branch, cb) {
      shipit.mute();
      shipit.local('git diff origin/master CHANGELOG.md', {
        cwd: shipit.config.local.path
      }, function(err, res) {
        shipit.unmute();

        if (err) return cb(err);

        shipit.log('CHANGELOG.md:');

        var lines = res.stdout.split('\n');
        lines.forEach(function(line){
          if (dups.indexOf(line) !== -1)
            console.log(chalk.magenta(line));
          else
            console.log(line);
        });

        if (dups.length > 0) {
          shipit.log(chalk.bgYellow('Duplicates are found!'));
          dups.forEach(function(str){
            shipit.log('dup:', str);
          });
        }

        inquirer.prompt([{
          type: 'confirm',
          message: 'Push branch "' + branch + '" to "origin/' + branch + '"?',
          name: 'confirm',
          default: isOK
        }], function(ans) {
          if (!ans.confirm) {
            shipit.log('task was canceled');
            isPush = false;
            return gitReset(branch, cb);
          }

          isPush = true;
          return cb(null);
        });
      });
    }

    function gitPush(branch, cb) {
      if (!isPush) return cb(null);

      shipit.log('> Pushing new version of "%s" to origin:', branch);
      shipit.local('git push origin ' + branch + ':' + branch, {
        cwd: shipit.config.local.path
      }, cb);
    }
  });
  function doTasks(selTasks) {
    enableLog(config.path.log);
    colorPrint('Selected tasks: "' + Object.keys(selTasks).join(' ') + '"', 'dim');
    var needFallback = false;

    seq(
      copy,
      update,
      changeLastWorked,
      changeCurRelease,
      removeOldCopies,
      restartServer,
      function(err) {
        if (err) {
          if (needFallback) {
            colorPrint('You should perform rollback to last working release. Don\'t run update task ' +
              'before you rollback to pervious configuration!', 'fgRed');
            colorPrint('To rollback make: ln -sf `readlink ' + lastWorkedDir + '` ' + config.path.app.replace(/\/$/, ''), 'fgRed');
          }
          return execError(err);
        }
        return gulpCallback();
      });

    function copy(cb) {
      console.log('Copying current app...');
      var tasks = [];
      if (fs.existsSync(startUpdateDir)) {
        console.log('Previous failed update found. Removing it...');
        tasks.push(cmdFactory('rm -Rf ' + startUpdateDir));
      }
      tasks.push(cmdFactory('cp -R ' + config.path.app + '/ ' + startUpdateDir));
      tasks.push(cb);
      seq.apply(this, tasks);
    }

    function update(cb) {
      console.log('Starting updates...');
      var tasks = [];
      for (var id in selTasks) {
        if (!selTasks.hasOwnProperty(id)) continue;

        tasks.push(cmdFactory('git pull && npm install', path.join(startUpdateDir, selTasks[id].path), 'updating ' + id));
      }
      tasks.push(cb);
      seq.apply(this, tasks);
    }

    function changeLastWorked(cb) {
      console.log('Saving link to current working app...');
      cmdFactory('rm -Rf ' + lastWorkedDir + ' && ln -s `readlink ' + config.path.app.replace(/\/$/, '') + '` ' + lastWorkedDir)(cb);
    }

    function changeCurRelease(cb) {
      console.log('Linking new release as current...');
      var curUpdateDir = path.join(config.path.releases, taskUid().get());
      seq(
        cmdFactory('mv ' + startUpdateDir + ' ' + curUpdateDir),
        cmdFactory('rm -R ' + config.path.app.replace(/\/$/, '') + ' && ln -s ' + curUpdateDir + ' ' + config.path.app.replace(/\/$/, '')),
        function(err, result) {
          if (err) return cb(err);

          needFallback = true;
          return cb(null);
        });
    }

    function removeOldCopies(cb) {
      console.log('Removing old copies...');
      var allDirs = getDirsSync(config.path.releases);
      var dirRegexp = /\w+-[\d\d\.]+-(\d+)/;

      var filtered = allDirs.filter(function(val) {
        return dirRegexp.test(val);
      });

      if (filtered.length <= config.maxBackups) return cb(null);

      var sorted = filtered.sort(function(a, b) {
        // reverse order
        return parseInt(b.match(dirRegexp)[1]) - parseInt(a.match(dirRegexp)[1]);
      });

      var tasks = [];
      sorted.slice(config.maxBackups, sorted.length).forEach(function(dirname) {
        tasks.push(cmdFactory('rm -Rf ' + path.join(config.path.releases, dirname)));
      });

      tasks.push(cb);
      seq.apply(this, tasks);
    }

    function restartServer(cb) {
      if (config.path.hasOwnProperty('restartScript')) {
        console.log('Running server restart script...');
        cmdFactory(config.path.restartScript, path.dirname(config.path.restartScript))(cb);
      } else {
        console.log('No server restart script found...');
        return cb(null);
      }
    }
  }