GCETestMachine.js

const exec = require("child-process-promise").exec;
const TestMachine = require("./TestMachine");

/**
 * Interacts with the Google Compute Engine to interact with machines to create testing environments.
*/
class GCETestMachine extends TestMachine {

  /**
   * @param {TestMachineOptions} opts options to configure the test machine.
  */
  constructor(opts) {
    super(opts);
    this.zone = "us-east1-d";
    this.ensureStartedTimeout = 60 * 1000;
  }

  /**
   * Creates a random alphabetical string to generate unique machine names.
   * @return {String} a random alphabetical string.
  */
  salt() {
    return Math.random().toString(36).slice(2);
  }

  /**
   * Fetches all running Google Compute Engine machines.
   * @return {Promise<Array>} resolves to the current running machines.  Each includes
   *   `{ id, name, zone, creationTimestamp }`.
  */
  getInstances() {
    return exec(`gcloud compute instances list --regexp "${this.opts.prefix}.*" --format='json'`)
      .then(res => res.stdout)
      .then(JSON.parse);
  }

  /**
   * Creates a new Google Compute Engine machine for testing.
   * @return {Promise} resolves when the machine has been created.
  */
  createInstance() {
    this._salt = this.salt();
    this._image = `${this.opts.prefix}${this._salt}`;
    return exec(`gcloud compute instances create "${this._image}" \
      --image-family ubuntu-1604-lts \
      --image-project ubuntu-os-cloud \
      --preemptible \
      --zone "${this.zone}" \
      --machine-type n1-standard-1 \
      --metadata production=false \
      --metadata-from-file user-data=cloud-config.yaml`)
      .then(() => {
        console.log("Created machine.  Ensuring SSH connection works.");
        return this.ensureStarted(this.ensureStartedTimeout);
      });
  }

  /**
   * Destroys the Google Compute Engine machine used for testing.
   * @return {Promise} resolves when the machine has been destroyed.
  */
  destroyInstance() {
    if(!this._image) { return Promise.resolve(); }
    return exec(`gcloud compute instances delete "${this._image}" --zone "${this.zone}" -q`);
  }

  /**
   * Runs an SSH connection to the current Coogle Compute Engine instance.
   * @param {String} command a command to execute on the remote machine.  If not provided, an interactive SSH session
   *   is started.
   * @return {Promise} resolves when the SSH connection terminates.
   * @throws {TimeoutError} if the SSH command doesn't return within the set timeout.
   * @todo Fix 'manual' SSH.
  */
  ssh(command) {
    let machine = this._image;
    if(this.opts.sshUser) {
      machine = `${this.opts.sshUser}@${machine}`;
    }
    if(command) {
      return exec(`gcloud compute ssh "${machine}" --quiet --zone "${this.zone}" --command "${command}"`);
    }
    else {
      console.log(`Run 'gcloud compute ssh "${machine}" --zone "${this.zone}'.`);
      const rl = require("readline").createInterface({ input: process.stdin, output: process.stdout });
      const askQuestion = (text) => new Promise(resolve => rl.question(text, resolve));
      return askQuestion("Press <enter> to continue. ")
        .then(() => {
          rl.close();
        });
      /*const spawned = spawn(`gcloud compute ssh "${this._image}" --zone "${this.zone}"`, {
        stdio: "pipe",
      });
      return spawned;*/
    }
  }

}

module.exports = GCETestMachine;