From ea70143805fa5279f64b0c4fd493d2fe237078d2 Mon Sep 17 00:00:00 2001
From: Justin Beckwith <justin.beckwith@gmail.com>
Date: Tue, 4 Sep 2018 10:57:10 -0700
Subject: [PATCH] Retry npm install in CI (#7)

---
 .../google-cloud-asset/.circleci/config.yml   | 29 +++++----
 .../.circleci/npm-install-retry.js            | 60 +++++++++++++++++++
 2 files changed, 79 insertions(+), 10 deletions(-)
 create mode 100755 packages/google-cloud-asset/.circleci/npm-install-retry.js

diff --git a/packages/google-cloud-asset/.circleci/config.yml b/packages/google-cloud-asset/.circleci/config.yml
index 41c82336dbb..80dcf7e67d9 100644
--- a/packages/google-cloud-asset/.circleci/config.yml
+++ b/packages/google-cloud-asset/.circleci/config.yml
@@ -64,7 +64,7 @@ jobs:
           name: Install and link the module
           command: |-
             mkdir -p /home/node/.npm-global
-            npm install
+            ./.circleci/npm-install-retry.js
           environment:
             NPM_CONFIG_PREFIX: /home/node/.npm-global
       - run: npm test
@@ -92,7 +92,7 @@ jobs:
           command: |
             cd samples/
             npm link ../
-            npm install
+            ./../.circleci/npm-install-retry.js
           environment:
             NPM_CONFIG_PREFIX: /home/node/.npm-global
       - run:
@@ -107,9 +107,7 @@ jobs:
     steps:
       - checkout
       - run: *npm_install_and_link
-      - run:
-          name: Build documentation.
-          command: npm run docs
+      - run: npm run docs
   sample_tests:
     docker:
       - image: 'node:8'
@@ -119,9 +117,11 @@ jobs:
       - run:
           name: Decrypt credentials.
           command: |
-            openssl aes-256-cbc -d -in .circleci/key.json.enc \
+            if ! [[ -z "${SYSTEM_TESTS_ENCRYPTION_KEY}" ]]; then
+              openssl aes-256-cbc -d -in .circleci/key.json.enc \
                 -out .circleci/key.json \
                 -k "${SYSTEM_TESTS_ENCRYPTION_KEY}"
+            fi
       - run: *npm_install_and_link
       - run: *samples_npm_install_and_link
       - run:
@@ -133,7 +133,10 @@ jobs:
             NPM_CONFIG_PREFIX: /home/node/.npm-global
       - run:
           name: Remove unencrypted key.
-          command: rm .circleci/key.json
+          command: |
+            if ! [[ -z "${SYSTEM_TESTS_ENCRYPTION_KEY}" ]]; then
+              rm .circleci/key.json
+            fi
           when: always
     working_directory: /home/node/samples/
   system_tests:
@@ -145,9 +148,11 @@ jobs:
       - run:
           name: Decrypt credentials.
           command: |
-            openssl aes-256-cbc -d -in .circleci/key.json.enc \
+            if ! [[ -z "${SYSTEM_TESTS_ENCRYPTION_KEY}" ]]; then
+              openssl aes-256-cbc -d -in .circleci/key.json.enc \
                 -out .circleci/key.json \
                 -k "${SYSTEM_TESTS_ENCRYPTION_KEY}"
+            fi
       - run: *npm_install_and_link
       - run:
           name: Run system tests.
@@ -156,7 +161,10 @@ jobs:
             GOOGLE_APPLICATION_CREDENTIALS: .circleci/key.json
       - run:
           name: Remove unencrypted key.
-          command: rm .circleci/key.json
+          command: |
+            if ! [[ -z "${SYSTEM_TESTS_ENCRYPTION_KEY}" ]]; then
+              rm .circleci/key.json
+            fi
           when: always
   publish_npm:
     docker:
@@ -164,5 +172,6 @@ jobs:
         user: node
     steps:
       - checkout
-      - run: 'echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc'
+      - run: ./.circleci/npm-install-retry.js
+      - run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
       - run: npm publish --access=public
diff --git a/packages/google-cloud-asset/.circleci/npm-install-retry.js b/packages/google-cloud-asset/.circleci/npm-install-retry.js
new file mode 100755
index 00000000000..ae3220d7348
--- /dev/null
+++ b/packages/google-cloud-asset/.circleci/npm-install-retry.js
@@ -0,0 +1,60 @@
+#!/usr/bin/env node
+
+let spawn = require('child_process').spawn;
+
+//
+//USE: ./index.js <ms npm can be idle> <number of attempts> [... NPM ARGS]
+//
+
+let timeout = process.argv[2] || 60000;
+let attempts = process.argv[3] || 3;
+let args = process.argv.slice(4);
+if (args.length === 0) {
+  args = ['install'];
+}
+
+(function npm() {
+  let timer;
+  args.push('--verbose');
+  let proc = spawn('npm', args);
+  proc.stdout.pipe(process.stdout);
+  proc.stderr.pipe(process.stderr);
+  proc.stdin.end();
+  proc.stdout.on('data', () => {
+    setTimer();
+  });
+  proc.stderr.on('data', () => {
+    setTimer();
+  });
+
+  // side effect: this also restarts when npm exits with a bad code even if it
+  // didnt timeout
+  proc.on('close', (code, signal) => {
+    clearTimeout(timer);
+    if (code || signal) {
+      console.log('[npm-are-you-sleeping] npm exited with code ' + code + '');
+
+      if (--attempts) {
+        console.log('[npm-are-you-sleeping] restarting');
+        npm();
+      } else {
+        console.log('[npm-are-you-sleeping] i tried lots of times. giving up.');
+        throw new Error("npm install fails");
+      }
+    }
+  });
+
+  function setTimer() {
+    clearTimeout(timer);
+    timer = setTimeout(() => {
+      console.log('[npm-are-you-sleeping] killing npm with SIGTERM');
+      proc.kill('SIGTERM');
+      // wait a couple seconds
+      timer = setTimeout(() => {
+        // its it's still not closed sigkill
+        console.log('[npm-are-you-sleeping] killing npm with SIGKILL');
+        proc.kill('SIGKILL');
+      }, 2000);
+    }, timeout);
+  }
+})();