Strider-CD/strider

View on GitHub
apps/strider/lib/models/upgrade/from0to1.js

Summary

Maintainability
F
2 wks
Test Coverage
const models = require('../');
const Job = models.Job;
const User = models.User;
const Project = models.Project;

const async = require('async');

const utils = require('../../utils');

module.exports = function (done) {
  upgradeUsers(function (err) {
    if (err) return done(err);
    upgradeJobs(done);
  });
};

function upgradeJobs(done) {
  Job.collection.find({}, function (err, cursor) {
    if (err) return done(err);
    cursor.toArray(function (err, jobs) {
      if (err) return done(err);
      console.log('converting', jobs.length, 'jobs');
      const tasks = [];
      jobs.forEach(function (job) {
        tasks.push(function (next) {
          upgradeJob(job, next);
        });
      });
      async.series(tasks, done);
    });
  });
}

function upgradeJob(job, done) {
  Job.findById(job._id)
    .lean()
    .exec(function (err, mjob) {
      if (err) return done(err);
      mjob.user_id = job._owner;
      mjob.project = makeName(job.repo_url);
      mjob.ref = {
        branch: 'master',
      };
      if (job.github_commit_info) {
        mjob.ref.id = job.github_commit_info.id;
      }
      mjob.std = {
        out: job.stdout || '',
        err: job.stderr || '',
        merged: job.stdmerged || '',
      };
      mjob.created = job.created_timestamp;
      mjob.finished = job.finished_timestamp;
      mjob.queued = mjob.created;
      mjob.started = mjob.created;
      mjob.duration = mjob.finished
        ? mjob.finished.getTime() - mjob.created.getTime()
        : 0;
      mjob.archived = job.archived_timestamp;
      mjob.trigger = makeTrigger(job, job.github_commit_info);
      mjob.plugin_data = {};
      if (job.tasks && job.tasks.length) {
        mjob.plugin_data.sauce = job.tasks;
      }
      killAttrs(mjob, [
        'created_timestamp',
        'finished_timestamp',
        'archived_timestamp',
        '_owner',
        'repo_url',
        'stdout',
        'stderr',
        'stdmerged',
        'tasks',
      ]);
      mjob.phases = {
        environment: {
          commands: [],
        },
        prepare: {
          commands: [],
        },
        test: {
          finished: mjob.finished,
          duration: mjob.duration,
          exitCode: mjob.test_exitcode,
          commands: [
            {
              started: mjob.started,
              duration: mjob.duration,
              command: 'Legacy job output',
              out: mjob.std.out,
              err: mjob.std.err,
              merged: mjob.std.merged,
            },
          ],
        },
        deploy: {
          commands: [],
        },
        cleanup: {
          commands: [],
        },
      };
      Job.collection.updateOne({ _id: job._id }, mjob, done);
      console.log('done job', job._id);
    });
}

function makeTrigger(job, commit) {
  if (!commit) {
    return {
      type: 'manual',
      author: {
        id: job._owner,
        // TODO get more info about the user? Like email, gravatar, etc
      },
      message: job.type === 'TEST_AND_DEPLOY' ? 'Redeploy' : 'Retest',
      timestamp: job.created_timestamp,
      source: { type: 'UI', page: 'Unknown' },
    };
  }
  commit.author.image = utils.gravatar(commit.author.email);
  return {
    type: 'commit',
    author: commit.author,
    message: commit.message,
    timestamp: commit.timestamp,
    url: `${job.repo_url}/commit/${commit.id}`,
    source: { type: 'plugin', plugin: 'github' },
  };
}

function upgradeUsers(done) {
  User.collection.find({}, function (err, cursor) {
    if (err) return done(err);
    cursor.toArray(function (err, users) {
      if (err) return done(err);
      console.log('converting users', users.length);
      const tasks = [];
      users.forEach(function (user) {
        tasks.push(function (next) {
          upgradeUser(user, next);
        });
      });
      async.series(tasks, done);
    });
  });
}

function makeGithubRepos(user) {
  if (!user.github) return [];
  const github = user.github_metadata[user.github.id].repos;
  const repos = [];
  for (let i = 0; i < github.length; i++) {
    repos.push({
      id: `${github[i].id}`,
      name: github[i].full_name && github[i].full_name.toLowerCase(),
      display_name: github[i].full_name,
      group: github[i].owner.login,
      display_url: github[i].html_url,
      config: {
        url: `git://${github[i].clone_url.split('//')[1]}`,
        owner: github[i].owner.login,
        repo: github[i].name,
        auth: {
          type: 'https',
        },
      },
    });
  }
  return repos;
}

// remove attributes from a model
function killAttrs(model, attrs) {
  for (let i = 0; i < attrs.length; i++) {
    delete model[attrs[i]];
  }
}

function makeHerokuAccounts(user) {
  if (!user.heroku || !user.heroku.length) return { accounts: [], apps: {} };
  const keys = {};
  const accounts = [];
  const caches = {};
  const apps = {};
  user.heroku.forEach(function (account) {
    let aid = account.account_id.split('@')[0];
    if (keys[account.api_key]) {
      aid = keys[account.api_key];
    } else {
      caches[account.api_key] = [];
    }
    apps[account.account_id] = {
      id: account.account_id.split('@')[0].split('-')[1],
      name: account.app,
      account: aid,
      git_url: `git@heroku.com:${account.app}.git`,
      web_url: `http://${account.app}.herokuapp.com/`,
    };
    caches[account.api_key].push(apps[account.account_id]);
    if (keys[account.api_key]) {
      return;
    }
    keys[account.api_key] = aid;
    accounts.push({
      id: aid,
      api_key: account.api_key,
      email: aid,
      privkey: account.privkey,
      pubkey: account.pubkey,
      cache: caches[account.api_key],
    });
  });
  return {
    accounts: accounts,
    apps: apps,
  };
}

function upgradeUser(user, done) {
  User.findById(user._id)
    .lean()
    .exec(function (err, mongUser) {
      const heroku = makeHerokuAccounts(user);
      mongUser.accounts = [];
      if (user.github && user.github.id) {
        mongUser.accounts.push({
          provider: 'github',
          id: user.github.id,
          display_url: `https://github.com/${user.github.login}`,
          title: user.github.login,
          config: convertGithub(user),
          cache: makeGithubRepos(user),
        });
      }
      mongUser.jobplugins = {
        heroku: {
          accounts: heroku.accounts,
        },
      };
      mongUser.projects = [];
      killAttrs(mongUser, [
        'github',
        'github_config',
        'github_metadata',
        'dotcloud_config',
        'heroku',
      ]);
      const projects = [];
      if (user.github_config && user.github_config.length > 0) {
        for (let i = 0; i < user.github_config.length; i++) {
          const repo_config = user.github_config[i];
          const name = makeName(repo_config.display_url);
          mongUser.projects.push({
            name: name.toLowerCase(),
            display_name: name,
            access_level: 2,
          });
          projects.push(makeProject(name, repo_config, user, heroku));
        }
      }
      Project.create(projects, function (err) {
        if (err) return done(err);
        User.collection.updateOne({ _id: mongUser._id }, mongUser, done);
      });
      console.log('projects', projects.length, user._id);
    });
}

function makeName(url) {
  return url.split('/').slice(-2).join('/').split('.')[0];
}

function makeProvider(name, repo, user) {
  // TODO: how do I know if it's not github?
  const parts = name.split('/');
  const repos = user.github_metadata[user.github.id].repos;
  let url = `git://${repo.display_url.split('//')[1]}.git`;
  let id = null;
  for (let i = 0; i < repos.length; i++) {
    if (repos[i].html_url === repo.display_url) {
      id = repos[i].id;
      url = `git://${repos[i].git_url.split('//')[1]}`;
      break;
    }
  }
  return {
    id: 'github',
    repo_id: id,
    account: user.github.id,
    config: {
      url: url,
      owner: parts[0],
      repo: parts[1],
      auth: {
        type: 'https',
        // with no username specified,
      },
    },
  };
}

// plugins that we need:
// node
//
// Optional:
// browserstack
// webhooks
// heroku
// custom
// env
// jelly
// qunit
// sauce

const checkPlugins = {
  sauce: function (repo) {
    if (!repo.sauce_access_key) return;
    return {
      access_key: repo.sauce_access_key,
      username: repo.sauce_username,
      browsers: repo.sauce_browsers,
    };
  },
  qunit: function (repo) {
    if (!repo.qunit_file) return;
    return {
      file: repo.qunit_file,
      path: repo.qunit_path,
    };
  },
  jelly: function (repo) {
    if (!repo.jelly_payload) return;
    return {
      url: repo.jelly_url,
      port: repo.jelly_port,
      payload: repo.jelly_payload,
      static: repo.jelly_static,
      static_dir: repo.jelly_static_dir,
    };
  },
  env: function (repo) {
    return repo.env;
  },
  custom: function (repo) {
    return repo.custom;
  },
  heroku: function (repo, user, heroku) {
    if (
      !repo.prod_deploy_target ||
      repo.prod_deploy_target.provider !== 'heroku'
    )
      return;
    const account_id = repo.prod_deploy_target.account_id;
    return { app: heroku.apps[account_id] };
  },
  webhooks: function (repo) {
    if (!repo.webhooks || !repo.webhooks.length) return;
    const hooks = [];
    let hook;
    for (let i = 0; i < repo.webhooks.length; i++) {
      hook = repo.webhooks[i];
      hooks.push({
        id: hook._id,
        url: hook.url,
        secret: hook.secret,
        format: '',
        trigger: 'job.done',
      });
    }
    return hooks;
  },
  browserstack: function (repo) {
    if (!repo.browserstack_api_key) return;
    return {
      apiKey: repo.browserstack_api_key,
      username: repo.browserstack_username,
      password: repo.browserstack_password,
      browsers: repo.browserstack_browsers,
    };
  },
};

function makePlugins(repo, user, heroku) {
  const plugins = [
    {
      id: 'node',
      enabled: true,
      config: {
        test: 'npm install',
        runtime: 'whatever',
      },
    },
  ];
  Object.keys(checkPlugins).forEach(function (name) {
    const config = checkPlugins[name](repo, user, heroku);
    if (!config) return;
    plugins.push({
      id: name,
      enabled: true,
      config: config,
    });
  });
  return plugins;
}

function makeBranch(repo, user, heroku) {
  return {
    active: repo.active,
    name: 'master',
    mirror_master: false,
    deploy_on_green:
      repo.prod_deploy_target && repo.prod_deploy_target.deploy_on_green,
    deploy_on_pull_request: false,
    pubkey: repo.pubkey,
    privkey: repo.privkey,
    plugins: makePlugins(repo, user, heroku),
    plugin_data: {},
    runner: {
      id: 'simple-runner',
      config: {
        pty: false,
      },
    },
  };
}

function makeProject(name, repo, user, heroku) {
  return {
    name: name.toLowerCase(),
    secret: repo.secret,
    public: repo.public,
    display_name: name,
    display_url: repo.display_url,
    creator: user._id,
    branches: [makeBranch(repo, user, heroku)],
    provider: makeProvider(name, repo, user),
  };
}

function convertGithub(user) {
  if (!user.github) return {};
  const g = user.github;
  return {
    accessToken: g.accessToken,
    login: g.login,
    email: g.email,
    gravatarId: g.gravatarId,
    name: g.name,
  };
}