cluster.js 7.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed 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.

/**
 * @fileoverview Gulp tasks for kubernetes cluster management.
 */
import childProcess from 'child_process';
import del from 'del';
import gulp from 'gulp';
import chmod from 'gulp-chmod';
import gulpFilter from 'gulp-filter';
import git from 'gulp-git';
import gunzip from 'gulp-gunzip';
import untar from 'gulp-untar';
import gulpUtil from 'gulp-util';
27
import pathExists from 'path-exists';
28 29 30 31 32
import request from 'request';
import source from 'vinyl-source-stream';

import conf from './conf';

33 34 35 36 37
const kubernetesArchive = 'kubernetes.tar.gz';
const kubernetesUrl = 'https://github.com/kubernetes/kubernetes.git';
const stableVersion = 'v1.1.1';
const tarballUrl = 'https://storage.googleapis.com/kubernetes-release/release';
const upScript = `${conf.paths.kubernetes}/hack/local-up-cluster.sh`;
38

39 40 41 42 43
/**
 * The healthz URL of the cluster to check that it is running.
 */
const clusterHealthzUrl = `http://${conf.backend.apiServerHost}/healthz`;

44 45 46 47 48 49 50
/**
 * Currently running cluster process object. Null if the cluster is not running.
 *
 * @type {?child.ChildProcess}
 */
let clusterProcess = null;

51 52 53 54 55
/**
 * @type {boolean} The variable is set when there is an error during cluster creation.
 */
let clusterSpawnFailure = null;

56 57 58 59 60 61 62 63
/**
 * A Number, representing the ID value of the timer that is set for function which periodically
 * checks if cluster is running. The null means that no timer is running.
 *
 * @type {?number}
 */
let isRunningSetIntervalHandler = null;

64 65 66 67 68 69 70 71 72 73 74 75 76
/**
 * Checks if there was a failure during cluster creation.
 * Produces an error in case there was one.
 * @param {function(?Error=)} doneFn - callback for the error
 */
function checkForClusterFailure(doneFn) {
  if (clusterSpawnFailure) {
    clearTimeout(isRunningSetIntervalHandler);
    isRunningSetIntervalHandler = null;
    doneFn(new Error('There was an error during cluster creation. Aborting.'));
  }
}

77 78 79 80 81 82
/**
 * Checks if cluster health check return correct status.
 * When custer is up and running then return 'ok'.
 * @param {function(?Error=)} doneFn
 */
function clusterHealthCheck(doneFn) {
83
  childProcess.exec(`curl ${clusterHealthzUrl}`, function(err, stdout) {
84 85 86 87 88
    if (err) {
      return doneFn(new Error(err));
    }
    return doneFn(stdout.trim());
  });
89 90 91 92 93 94 95 96
}

/**
 * Executes controls command using kubectl.
 * @param {string} command
 * @param {function(?Error=)} doneFn
 */
function executeKubectlCommand(command, doneFn) {
97
  childProcess.exec(`${conf.paths.kubernetes}/cluster/kubectl.sh ${command}`, function(err) {
98 99 100
    if (err) return doneFn(new Error(err));
    doneFn();
  });
101 102 103 104 105 106 107 108 109 110 111 112 113
}

/**
 * Creates cluster from scratch.
 * Downloads latest version of kubernetes from git repository.
 * Checkouts for latest release.
 * Executes script to up cluster.
 * Prerequisites:
 *  * Install Docker for your OS
 *  * Pull golang docker image: docker pull golang:1.4
 *  * Install golang
 *  * Install etcd
 */
114
gulp.task('local-up-cluster', ['spawn-cluster']);
115 116 117 118

/**
 * Tears down a Kubernetes cluster.
 */
119
gulp.task('kill-cluster', function(doneFn) {
120
  if (clusterProcess) {
121
    clusterProcess.on('exit', function() {
122 123 124 125 126 127 128
      clusterProcess = null;
      doneFn();
    });
    clusterProcess.kill();
  } else {
    doneFn();
  }
129 130 131 132 133
});

/**
 * Clones kubernetes from git repository. Task skip if kubernetes directory exist.
 */
134 135
gulp.task('clone-kubernetes', function(doneFn) {
  pathExists(conf.paths.kubernetes).then(function(exists) {
136
    if (!exists) {
137 138
      git.clone(kubernetesUrl, {args: conf.paths.kubernetes}, function(err) {
        if (err) return doneFn(new Error(err));
139 140 141 142 143 144
        doneFn();
      });
    } else {
      doneFn();
    }
  });
145 146 147 148 149
});

/**
 * Checkouts kubernetes to latest stable version.
 */
150 151 152
gulp.task('checkout-kubernetes-version', ['clone-kubernetes'], function(doneFn) {
  git.checkout(stableVersion, {cwd: conf.paths.kubernetes, quiet: true}, function(err) {
    if (err) return doneFn(new Error(err));
153 154
    doneFn();
  });
155 156 157 158 159 160
});

/**
 * Checks if kubectl is already downloaded.
 * If not downloads kubectl for all platforms from tarball.
 */
161
gulp.task('download-kubectl', function(doneFn) {
162
  let filter = gulpFilter('**/platforms/**');
163
  pathExists(`${conf.paths.kubernetes}/platforms`).then(function(exists) {
164 165
    if (!exists) {
      request(`${tarballUrl}/${stableVersion}/${kubernetesArchive}`)
166 167 168 169 170
          .pipe(source(`${kubernetesArchive}`))
          .pipe(gunzip())
          .pipe(untar())
          .pipe(filter)
          .pipe(chmod(755))
171
          .pipe(gulp.dest(conf.paths.base))
172
          .on('end', function() { doneFn(); });
173 174 175 176
    } else {
      doneFn();
    }
  });
177 178 179 180 181
});

/**
 * Removes kubernetes before git clone command.
 */
182
gulp.task('clear-kubernetes', function() { return del(conf.paths.kubernetes); });
183 184 185 186

/**
 * Spawns local-up-cluster.sh script.
 */
187 188 189 190 191 192 193 194 195 196 197 198
gulp.task(
    'spawn-cluster',
    [
      'checkout-kubernetes-version',
      'kubeconfig-set-cluster-local',
      'kubeconfig-set-context-local',
      'kubeconfig-use-context-local',
      'kill-cluster',
    ],
    function() {
      clusterProcess = childProcess.spawn(upScript, {stdio: 'inherit'});

199 200 201 202 203 204
      clusterProcess.on('exit', function(code) {
        if (code !== 0) {
          clusterSpawnFailure = code;
        }
        clusterProcess = null;
      });
205 206 207 208 209
    });

/**
 * Checks periodically if cluster is up and running.
 */
210
gulp.task('wait-for-cluster', function(doneFn) {
211
  let counter = 0;
212
  if (!isRunningSetIntervalHandler) {
213 214
    isRunningSetIntervalHandler = setInterval(isRunning, 1000);
  }
215 216

  function isRunning() {
217
    if (counter % 10 === 0) {
218 219 220
      gulpUtil.log(
          gulpUtil.colors.magenta(
              `Waiting for a Kubernetes cluster on ${conf.backend.apiServerHost}...`));
221 222 223 224 225 226
    }
    counter += 1;

    checkForClusterFailure(doneFn);

    // constantly query the cluster until it is properly running
227
    clusterHealthCheck(function(result) {
228
      if (result === 'ok') {
229
        gulpUtil.log(gulpUtil.colors.magenta('Kubernetes cluster is up and running.'));
230 231
        clearTimeout(isRunningSetIntervalHandler);
        isRunningSetIntervalHandler = null;
232 233
        doneFn();
      }
234
    });
235 236
  }
});
237 238 239 240 241

/**
 * Sets a cluster entry in kubeconfig.
 * Configures kubernetes server for localhost.
 */
242 243 244 245
gulp.task(
    'kubeconfig-set-cluster-local', ['download-kubectl', 'checkout-kubernetes-version'],
    function(doneFn) {
      executeKubectlCommand(
246 247
          `config set-cluster local --server=http://${conf.backend.apiServerHost}` +
              `--insecure-skip-tls-verify=true`,
248 249
          doneFn);
    });
250 251 252 253

/**
 * Sets a context entry in kubeconfig as local.
 */
254 255 256 257 258
gulp.task(
    'kubeconfig-set-context-local', ['download-kubectl', 'checkout-kubernetes-version'],
    function(doneFn) {
      executeKubectlCommand('config set-context local --cluster=local', doneFn);
    });
259 260 261 262

/**
 * Sets the current-context in a kubeconfig file
 */
263 264 265
gulp.task(
    'kubeconfig-use-context-local', ['download-kubectl', 'checkout-kubernetes-version'],
    function(doneFn) { executeKubectlCommand('config use-context local', doneFn); });