问题
I am testing a Cloud Function in GCP and I want to modify labels of my compute instance with Cloud Function i.e. change label "status=active" to "status=tobedeleted".
Is there a way to do it with Cloud Function and node.js ?
Looks as if the method compute.instances.setLabels requires additional libraries ?
I already created Cloud Function to stop/start instances.
Here is the error :
resource: {…}
severity: "ERROR"
textPayload: "{ Error: Login Required at Gaxios.request (/srv/node_modules/googleapis-common/node_modules/gaxios/build/src/gaxios.js:70:23) at at process._tickDomainCallback (internal/process/next_tick.js:229:7) response: { config: { url: 'https://www.googleapis.com/compute/v1/projects/wpress-v1/zones/us-central1-a/instances/instance-1/setLabels?labels%5Bis-scheduled%5D=manual', method: 'POST', paramsSerializer: [Function], headers: [Object], params: [Object], validateStatus: [Function], retry: true, responseType: 'json', retryConfig: [Object] }, data: { error: [Object] },
Then here is my code :
const Compute = require('@google-cloud/compute');
/*const compute = new Compute();*/
const {google} = require('googleapis');
/*const google = require('@google-cloud/googleapis');*/
var compute = google.compute('v1');
exports.setInstanceScheduleMode = (event, context, callback) => {
try {
const payload = _validatePayload(
JSON.parse(Buffer.from(event.data, 'base64').toString())
);
var request = {
project: 'wpress-v1',
zone: 'us-central1-a',
instance: 'instance-1',
labels: {
"is-scheduled": "manual"
},
auth: google.authClient,
};
compute.instances.setLabels(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
} catch (err) {
console.log(err);
callback(err);
}
};
// [END functions_start_instance_pubsub]
function _validatePayload(payload) {
if (!payload.zone) {
throw new Error(`Attribute 'zone' missing from payload`);
} else if (!payload.label) {
throw new Error(`Attribute 'label' missing from payload`);
}
else if (!payload.instance) {
throw new Error(`Attribute 'instance' missing from payload`);
}
return payload;
}
function authorize(callback) {
google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/cloud-platform']
}).then(client => {
callback(client);
}).catch(err => {
console.error('authentication failed: ', err);
});
}
回答1:
Google Cloud Platform documentation provides a detailed overview of instances.setLabels method, which is a part of Google's Node.js client library.
See the Node.js code sample mentioned in GCP documentation below:
// BEFORE RUNNING:
// ---------------
// 1. If not already done, enable the Compute Engine API
// and check the quota for your project at
// https://console.developers.google.com/apis/api/compute
// 2. This sample uses Application Default Credentials for authentication.
// If not already done, install the gcloud CLI from
// https://cloud.google.com/sdk and run
// `gcloud beta auth application-default login`.
// For more information, see
// https://developers.google.com/identity/protocols/application-default-credentials
// 3. Install the Node.js client library by running
// `npm install googleapis --save`
const {google} = require('googleapis');
var compute = google.compute('v1');
authorize(function(authClient) {
var request = {
// Project ID for this request.
project: 'my-project', // TODO: Update placeholder value.
// The name of the zone for this request.
zone: 'my-zone', // TODO: Update placeholder value.
// Name of the instance scoping this request.
instance: 'my-instance', // TODO: Update placeholder value.
resource: {
// TODO: Add desired properties to the request body.
},
auth: authClient,
};
compute.instances.setLabels(request, function(err, response) {
if (err) {
console.error(err);
return;
}
// TODO: Change code below to process the `response` object:
console.log(JSON.stringify(response, null, 2));
});
});
function authorize(callback) {
google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/cloud-platform']
}).then(client => {
callback(client);
}).catch(err => {
console.error('authentication failed: ', err);
});
}
Remember to send the request body as a parameter when writing your code.
Consider the following when using this method:
- You will need to specify the current labels your instance has under labelFingerprint.
- Your instance's labels will be overwritten, so make sure to include in request body any labels you'd want to keep.
回答2:
There's a good number of things going on in this code. This isn't a straightforward operation, and I do wish there were a few more examples in the documentation of how to do this.
First, it appears that the @google-cloud/compute
idiomatic library doesn't support a setLabels
function on its VMs object, so we're forced to use the node REST library, which isn't quite as easy to use. The code you have written seems to mix the two in a somewhat confusing way, but is mostly already using the REST API so we can start from there. For reference, the setLabels REST API documentation.
Second, the authentication error you are getting is because you haven't properly intilized the authClient for the REST API, in particular by granting it the correct scope. (Notably, the authorize()
method is never called, unlike in the sample code). This needs to be called to at least request the https://www.googleapis.com/auth/compute
scope, though the cloud-platform
scope will also work, as it is more privileged. This is what is leading to your immediate authentication error.
It is also possible that you are running the cloud function as an IAM account without the necessary roles, but both the default compute engine and default app engine accounts should be able to do this, so it appears to be that the scopes aren't requested.
Finally, even if this was working, you would find that the setLabels method requires a fingerprint of the current label values, or it would return a CONDITION_FAILURE
-- essentially, when you call setLabels you are fully replacing the labels on the instance, so the API wants to make sure two callers aren't competing at once.
All together, that leads us to this (for simplicity, I used an HTTP function, but of course you can use your existing trigger just as well):
const { google } = require('googleapis');
const computeClient = google.compute('v1');
exports.labelInstance = async (req, res) => {
// First, get the auth scope we need. Thankfully cloud functions runs with
// application default credentials, so we don't need to do anything with keys, etc
// as long as the service account we are configured to run as has the right permissions.
//
// We only need the compute scope, we don't need all of cloud-platform, so limit ourselves to that.
const auth = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/compute']
});
const authClient = await auth.getClient();
// Build our request
var baseRequest = {
project: 'YOUR-PROJECT-NAME',
zone: 'us-central1-a',
instance: 'instance-1',
auth: authClient
};
// We need to get the existing labels and fingerprint first.
return computeClient.instances.get(baseRequest).then(result => {
// We need all the fields from baseRequest again, and we want to keep the old labels.
// I'm sort of cheating here, since it isn't a deep copy, but it works within the
// scope of this function.
setRequest = baseRequest;
// As setLabels is a POST request, we need to put the parameters in the requestBody.
setRequest.requestBody = {
labels: result.data.labels || {},
labelFingerprint: result.data.labelFingerprint // Needed to avoid CONDITION_FAILURE
};
// And add our new label...
setRequest.requestBody.labels['my-new-label'] = 'my-new-value';
return computeClient.instances.setLabels(setRequest);
}).then(result => {
console.log('set done');
console.log(result);
return res.send('ok');
}).catch(error => {
console.error('Error!');
console.error(error);
return res.send('error');
});
};
In your original question you wanted to change a label. Obviously you can adjust the code above to remove any labels out of the set that is retrieved with the fingerprint that you like, you don't have to copy them all.
Also be aware that the above code doesn't actually wait for the operation to complete (as operations are asynchronous -- the result that is returned will likely be in the RUNNING state), you would need to further use the REST API to check on the status of the operation. I haven't done that as it is somewhat outside the scope of this question, but you can read about it here.
来源:https://stackoverflow.com/questions/57903961/how-can-i-change-a-vm-instance-label-with-gcp-cloud-function-using-node-js