Skip to content

Commit 5699b14

Browse files
authored
Refactor to manage Cloud SQL instances with CloudEvents
1 parent 185389e commit 5699b14

1 file changed

Lines changed: 111 additions & 108 deletions

File tree

functions/scheduleinstance/index.js

Lines changed: 111 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -14,134 +14,137 @@
1414

1515
// [START functions_start_instance_pubsub]
1616
// [START functions_stop_instance_pubsub]
17-
const compute = require('@google-cloud/compute');
18-
const instancesClient = new compute.InstancesClient();
19-
const operationsClient = new compute.ZoneOperationsClient();
20-
21-
async function waitForOperation(projectId, operation) {
22-
while (operation.status !== 'DONE') {
23-
[operation] = await operationsClient.wait({
24-
operation: operation.name,
25-
project: projectId,
26-
zone: operation.zone.split('/').pop(),
27-
});
28-
}
29-
}
17+
const functions = require('@google-cloud/functions-framework');
18+
const {SqlInstancesServiceClient} = require('@google-cloud/sql');
19+
const sqlInstancesClient = new SqlInstancesServiceClient({ fallback: true });
3020
// [END functions_stop_instance_pubsub]
3121

3222
/**
33-
* Starts Compute Engine instances.
23+
* Starts Cloud SQL instances.
3424
*
35-
* Expects a PubSub message with JSON-formatted event data containing the
36-
* following attributes:
37-
* zone - the GCP zone the instances are located in.
38-
* label - the label of instances to start.
25+
* Expects a PubSub message with JSON-formatted event data containing
26+
* the label of instances to start.
3927
*
40-
* @param {!object} event Cloud Function PubSub message event.
41-
* @param {!object} callback Cloud Function PubSub callback indicating
42-
* completion.
4328
*/
44-
exports.startInstancePubSub = async (event, context, callback) => {
45-
try {
46-
const project = await instancesClient.getProjectId();
47-
const payload = _validatePayload(event);
48-
const options = {
49-
filter: `labels.${payload.label}`,
50-
project,
51-
zone: payload.zone,
52-
};
53-
54-
const [instances] = await instancesClient.list(options);
55-
56-
await Promise.all(
57-
instances.map(async instance => {
58-
const [response] = await instancesClient.start({
59-
project,
60-
zone: payload.zone,
61-
instance: instance.name,
62-
});
63-
64-
return waitForOperation(project, response.latestResponse);
65-
})
66-
);
67-
68-
// Operation complete. Instance successfully started.
69-
const message = 'Successfully started instance(s)';
70-
console.log(message);
71-
callback(null, message);
72-
} catch (err) {
73-
console.log(err);
74-
callback(err);
29+
functions.cloudEvent('startInstanceEvent', async cloudEvent => {
30+
try {
31+
const payload = _validatePayload(cloudEvent);
32+
const project = await sqlInstancesClient.getProjectId();
33+
const [labelKey, labelValue] = payload.label.split('=');
34+
const filter = `settings.userLabels.${labelKey}:${labelValue}`;
35+
36+
console.log(`Attempting to start instances. Project ID resolved to: '${project}'. Filter applied: '${filter}'`);
37+
38+
// Fetch the response object
39+
const [response] = await sqlInstancesClient.list({
40+
project,
41+
filter,
42+
});
43+
44+
// Extract the array from the 'items' property (default to empty array if undefined)
45+
const instances = response.items || [];
46+
47+
console.log(`Raw instances array retrieved. Found ${instances.length} matching instances.`);
48+
49+
if (instances.length === 0) {
50+
console.log(`No SQL instances found in project '${project}' matching filter '${filter}'.`);
51+
return;
7552
}
76-
};
53+
54+
await Promise.all(
55+
instances.map(async instance => {
56+
console.log(`Starting Cloud SQL instance: ${instance.name}`);
57+
const request = {
58+
project,
59+
instance: instance.name,
60+
body: {
61+
settings: {
62+
activationPolicy: 'ALWAYS',
63+
},
64+
},
65+
};
66+
const [operation] = await sqlInstancesClient.patch(request);
67+
console.log(`Patch operation started for ${instance.name}: ${operation.name}`);
68+
})
69+
);
70+
} catch (err) {
71+
console.error(err);
72+
throw err;
73+
}
74+
});
7775
// [END functions_start_instance_pubsub]
7876
// [START functions_stop_instance_pubsub]
7977

8078
/**
81-
* Stops Compute Engine instances.
79+
* Stops Cloud SQL instances.
8280
*
83-
* Expects a PubSub message with JSON-formatted event data containing the
84-
* following attributes:
85-
* zone - the GCP zone the instances are located in.
86-
* label - the label of instances to stop.
81+
* Expects a PubSub message with JSON-formatted event data containing
82+
* the label of instances to stop.
8783
*
88-
* @param {!object} event Cloud Function PubSub message event.
89-
* @param {!object} callback Cloud Function PubSub callback indicating completion.
9084
*/
91-
exports.stopInstancePubSub = async (event, context, callback) => {
92-
try {
93-
const project = await instancesClient.getProjectId();
94-
const payload = _validatePayload(event);
95-
const options = {
96-
filter: `labels.${payload.label}`,
97-
project,
98-
zone: payload.zone,
99-
};
100-
101-
const [instances] = await instancesClient.list(options);
102-
103-
await Promise.all(
104-
instances.map(async instance => {
105-
const [response] = await instancesClient.stop({
106-
project,
107-
zone: payload.zone,
108-
instance: instance.name,
109-
});
110-
111-
return waitForOperation(project, response.latestResponse);
112-
})
113-
);
114-
115-
// Operation complete. Instance successfully stopped.
116-
const message = 'Successfully stopped instance(s)';
117-
console.log(message);
118-
callback(null, message);
119-
} catch (err) {
120-
console.log(err);
121-
callback(err);
85+
functions.cloudEvent('stopInstanceEvent', async cloudEvent => {
86+
try {
87+
const payload = _validatePayload(cloudEvent);
88+
const project = await sqlInstancesClient.getProjectId();
89+
const [labelKey, labelValue] = payload.label.split('=');
90+
const filter = `settings.userLabels.${labelKey}:${labelValue}`;
91+
92+
console.log(`Attempting to stop instances. Project ID resolved to: '${project}'. Filter applied: '${filter}'`);
93+
94+
// Fetch the response object
95+
const [response] = await sqlInstancesClient.list({
96+
project,
97+
filter,
98+
});
99+
100+
// Extract the array from the 'items' property (default to empty array if undefined)
101+
const instances = response.items || [];
102+
103+
console.log(`Raw instances array retrieved. Found ${instances.length} matching instances.`);
104+
105+
if (instances.length === 0) {
106+
console.log(`No SQL instances found in project '${project}' matching filter '${filter}'.`);
107+
return;
122108
}
123-
};
109+
110+
await Promise.all(
111+
instances.map(async instance => {
112+
console.log(`Stopping Cloud SQL instance: ${instance.name}`);
113+
const request = {
114+
project,
115+
instance: instance.name,
116+
body: {
117+
settings: {
118+
activationPolicy: 'NEVER',
119+
},
120+
},
121+
};
122+
const [operation] = await sqlInstancesClient.patch(request);
123+
console.log(`Patch operation started for ${instance.name}: ${operation.name}`);
124+
})
125+
);
126+
} catch (err) {
127+
console.error(err);
128+
throw err;
129+
}
130+
});
124131
// [START functions_start_instance_pubsub]
125132

126133
/**
127134
* Validates that a request payload contains the expected fields.
128-
*
129-
* @param {!object} payload the request payload to validate.
130-
* @return {!object} the payload object.
131135
*/
132-
const _validatePayload = event => {
133-
let payload;
134-
try {
135-
payload = JSON.parse(Buffer.from(event.data, 'base64').toString());
136-
} catch (err) {
137-
throw new Error('Invalid Pub/Sub message: ' + err);
138-
}
139-
if (!payload.zone) {
140-
throw new Error("Attribute 'zone' missing from payload");
141-
} else if (!payload.label) {
142-
throw new Error("Attribute 'label' missing from payload");
143-
}
144-
return payload;
136+
const _validatePayload = cloudEvent => {
137+
let payload;
138+
try {
139+
const base64Data = cloudEvent.data.message.data;
140+
payload = JSON.parse(Buffer.from(base64Data, 'base64').toString());
141+
} catch (err) {
142+
throw new Error('Invalid CloudEvent / Pub/Sub message: ' + err);
143+
}
144+
if (!payload.label) {
145+
throw new Error("Attribute 'label' missing from payload");
146+
}
147+
return payload;
145148
};
146149
// [END functions_start_instance_pubsub]
147150
// [END functions_stop_instance_pubsub]

0 commit comments

Comments
 (0)