0
votes

I'm trying to start a Compute Engine instance in a GCP project from a Cloud Function hosted in another GCP project. It works well, but I can't add compute engine instance metadata, I get a HTTP error code 400.

The Cloud Function (Node.js, with the compute engine client):

const Compute = require('@google-cloud/compute');
const compute = new Compute({
  projectId: 'myGcpProject'
});

exports.startVmInstance = async (data, context, callback) => {
  let zone = compute.zone('us-central1-a');
  let vm = zone.vm('myVmInstance');
  let metadata = {
    'myMeta': 'myValue'
  };

  // THIS PART HAS NO EFFECT IN THE COMPUTE ENGINE INSTANCE
  await vm.setMetadata(metadata).then((response) => {
    console.info('Set metadata response: ' + JSON.stringify(response[1]));
  }).catch((error) => {
    throw 'Error while setting metadata to VM instance: ' + error;
  });

  await vm.start().then(() => {
    console.info('Compute Engine instance started successfully');
  }).catch(error => {
    throw 'Error while starting Compute Engine instance: ' + error;
  });

  callback();
};

Logs:

- Function execution took 2235 ms, finished with status: 'ok'
- Compute Engine instance started successfully 
- Set metadata response: {"id":"84345484841776xxxxx","name":"operation-1587175048337-5a386fcf3d737-36e59a28-332a95f5","zone":"https://www.googleapis.com/compute/v1/projects/myGcpProject/zones/us-central1-a","operationType":"setMetadata","targetLink":"https://www.googleapis.com/compute/v1/projects/myGcpProject/zones/us-central1-a/instances/myVmInstance","targetId":"48593751438611xxxxx","status":"RUNNING","user":"[email protected]","progress":0,"insertTime":"2020-04-17T18:57:28.704-07:00","startTime":"2020-04-17T18:57:28.721-07:00","selfLink":"https://www.googleapis.com/compute/v1/projects/myGcpProject/zones/us-central1-a/operations/operation-1587175048337-5a386fcf3d737-36e59a28-332a95f5","kind":"compute#operation"} 
- Function execution started

And when I log in the Compute Engine instance with SSH, I am not able to read the metadata 'myMeta' (only the startup-script that I have by default):

$ gcloud compute instances describe myVmInstance
...
metadata:
  fingerprint: 0ay6MSNkjI8=
  items:
  - key: startup-script
    value: |-
      #!/bin/bash
      echo `date`
  kind: compute#metadata
...

I can see the operation but I get a HTTP error code 400

$ gcloud compute operations list | grep operation-1587175048337-5a386fcf3d737-36e59a28-332a95f5
operation-1587175048337-5a386fcf3d737-36e59a28-332a95f5    setMetadata    us-central1-a/instances/myVmInstance  400          DONE    2020-04-17T18:57:28.704-07:00

The service account used by the Cloud Function has the role Compute Admin in the GCP hosting the Compute Engine instance.

What am I missing?

1
SetMetadata is returning an operation token. This means that you need to wait for the operation to complete. Also, review your log messages. The SetMetadata response is printed after the VM start message.John Hanley

1 Answers

1
votes

@John Hanley is right, I need to wait for the operation to complete.

Also, even though the service account used by the Cloud Function is granted the role Compute Admin on the GCP project hosting the Compute Engine instance, it also needs to be granted the role Service Account User on the service account used by the Compute Engine instance.

Otherwise I get the error below

Error: The user does not have access to service account '[email protected]'. User: '[email protected]'. Ask a project owner to grant you the iam.serviceAccountUser role on the service account

The cloud function code (Node.js):

const Compute = require('@google-cloud/compute');
const compute = new Compute({
  projectId: 'myGcpProject'
});

exports.startVmInstance = async (data, context, callback) => {
  let zone = compute.zone('us-central1-a');
  let vm = zone.vm('myVmInstance');
  let metadata = {
    'myMeta': 'myValue'
  };

  let setMetadataOperation;
  await vm.setMetadata(metadata).then((response) => {
    setMetadataOperation = response[0];
    console.info('Set metadata response: ' + JSON.stringify(response[1]));
  }).catch((error) => {
    throw 'Error while setting metadata to VM instance: ' + error;
  });

  await setMetadataOperation.promise().then(() => {
    console.info('The operation setMetadata successfully completed');
  }).catch((error) => {
    throw 'Error with the operation setMetadata: ' + error;
  });

  await vm.start().then(() => {
    console.info('Compute Engine instance started successfully');
  }).catch(error => {
    throw 'Error while starting Compute Engine instance: ' + error;
  });

  callback();
};