From 7bcb79ce515b56b0a2c5aee2aadda4115ddb4fe7 Mon Sep 17 00:00:00 2001 From: Stephen Date: Mon, 10 Aug 2020 10:26:16 -0400 Subject: [PATCH] feat: add vm.update() method (#478) --- src/vm.js | 59 ++++++++++++++++++++++++++++ system-test/compute.js | 6 +++ test/vm.js | 89 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) diff --git a/src/vm.js b/src/vm.js index 6039352d..399a06f1 100644 --- a/src/vm.js +++ b/src/vm.js @@ -17,6 +17,7 @@ 'use strict'; const common = require('@google-cloud/common'); +const extend = require('extend'); const format = require('string-format-obj'); const is = require('is'); const {promisifyAll} = require('@google-cloud/promisify'); @@ -920,6 +921,64 @@ class VM extends common.ServiceObject { callback || common.util.noop ); } + /** + * Update the instance. + * + * NOTE: This method will pull the latest record of the current metadata, then + * merge it with the object you provide. This means there is a chance of a + * mismatch in data if the resource is updated immediately after we pull the + * metadata, but before we update it. + * + * @see [Instances: update API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/instances/update} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {Operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * const Compute = require('@google-cloud/compute'); + * const compute = new Compute(); + * const zone = compute.zone('zone-name'); + * const vm = zone.vm('vm-name'); + * + * const metadata = { + * deletionProtection: false, + * }; + * + * vm.update(metadata, function(err, operation, apiResponse) { + * // `operation` is an Operation object that can be used to check the + * // status of the request. + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * vm.update(metadata).then(function(data) { + * const operation = data[0]; + * const apiResponse = data[1]; + * }); + */ + update(metadata, callback) { + callback = callback || common.util.noop; + + this.getMetadata((err, currentMetadata) => { + if (err) { + callback(err); + return; + } + + this.request( + { + method: 'PUT', + uri: '', + json: extend(true, currentMetadata, metadata), + }, + callback + ); + }); + } /** * This function will callback when the VM is in the specified state. * diff --git a/system-test/compute.js b/system-test/compute.js index de89b316..dba06750 100644 --- a/system-test/compute.js +++ b/system-test/compute.js @@ -1021,6 +1021,12 @@ describe('Compute', () => { assert.deepStrictEqual(vm.metadata.metadata.items, [{key, value}]); }); + it('should update the VM', async () => { + await awaitResult(vm.update({deletionProtection: false})); + const [metadata] = await vm.getMetadata(); + assert.strictEqual(metadata.deletionProtection, false); + }); + it('should allow updating old metadata', async () => { const key = 'newKey'; const value = 'newValue'; diff --git a/test/vm.js b/test/vm.js index 7d3e0e06..229792e4 100644 --- a/test/vm.js +++ b/test/vm.js @@ -868,6 +868,95 @@ describe('VM', () => { }); }); + describe('update', () => { + const CURRENT_METADATA = {currentProperty: true}; + const METADATA = {newProperty: true}; + + beforeEach(() => { + vm.getMetadata = callback => { + callback(null, CURRENT_METADATA); + }; + vm.request = () => {}; + }); + + it('should pull the latest metadata', done => { + vm.getMetadata = () => { + done(); + }; + + vm.update(METADATA, assert.ifError); + }); + + it('should return an error if the metadata call failed', done => { + const error = new Error('Error.'); + + vm.getMetadata = callback => { + callback(error); + }; + + vm.update(METADATA, err => { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should send the correct request', done => { + vm.request = reqOpts => { + assert.deepStrictEqual(reqOpts, { + method: 'PUT', + uri: '', + json: extend(true, CURRENT_METADATA, METADATA), + }); + done(); + }; + + vm.update(METADATA, assert.ifError); + }); + + it('should deep merge the current metadata with new metadata', done => { + const currentMetadata = { + a: { + b: { + c: true, + d: true, + }, + }, + }; + const newMetadata = { + a: { + b: { + c: false, + }, + }, + }; + const expectedMetadata = { + a: { + b: { + c: false, + d: true, + }, + }, + }; + + vm.getMetadata = callback => { + callback(null, currentMetadata); + }; + + vm.request = reqOpts => { + assert.deepStrictEqual(reqOpts.json, expectedMetadata); + done(); + }; + + vm.update(newMetadata, assert.ifError); + }); + + it('should not require a callback', () => { + assert.doesNotThrow(() => { + vm.update(METADATA); + }); + }); + }); + describe('waitFor', () => { const VALID_STATUSES = [ 'PROVISIONING',