Skip to content

Commit ab2d996

Browse files
authored
Composer: Move 'triggering DAGs' sample to GitHub. (#716)
* Composer: Move 'triggering DAGs' sample to GitHub. Sample was originally hard-coded in HTML at https://cloud.google.com/composer/docs/how-to/using/triggering-with-gcf * Use node-fetch for promises in HTTP requests.
1 parent ade02a3 commit ab2d996

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed
+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// [START composer_trigger]
18+
'use strict';
19+
20+
const fetch = require('node-fetch');
21+
const FormData = require('form-data');
22+
23+
/**
24+
* Triggered from a message on a Cloud Storage bucket.
25+
*
26+
* IAP authorization based on:
27+
* https://stackoverflow.com/questions/45787676/how-to-authenticate-google-cloud-functions-for-access-to-secure-app-engine-endpo
28+
* and
29+
* https://cloud.google.com/iap/docs/authentication-howto
30+
*
31+
* @param {!Object} event The Cloud Functions event.
32+
* @param {!Function} callback The callback function.
33+
*/
34+
exports.triggerDag = function triggerDag (event, callback) {
35+
// Fill in your Composer environment information here.
36+
37+
// The project that holds your function
38+
const PROJECT_ID = 'your-project-id';
39+
// Navigate to your webserver's login page and get this from the URL
40+
const CLIENT_ID = 'your-iap-client-id';
41+
// This should be part of your webserver's URL:
42+
// {tenant-project-id}.appspot.com
43+
const WEBSERVER_ID = 'your-tenant-project-id';
44+
// The name of the DAG you wish to trigger
45+
const DAG_NAME = 'composer_sample_trigger_response_dag';
46+
47+
// Other constants
48+
const WEBSERVER_URL = `https://${WEBSERVER_ID}.appspot.com/api/experimental/dags/${DAG_NAME}/dag_runs`;
49+
const USER_AGENT = 'gcf-event-trigger';
50+
const BODY = {'conf': JSON.stringify(event.data)};
51+
52+
// Make the request
53+
authorizeIap(CLIENT_ID, PROJECT_ID, USER_AGENT)
54+
.then(function iapAuthorizationCallback (iap) {
55+
makeIapPostRequest(WEBSERVER_URL, BODY, iap.idToken, USER_AGENT, iap.jwt);
56+
})
57+
.then(_ => callback(null))
58+
.catch(callback);
59+
};
60+
61+
/**
62+
* @param {string} clientId The client id associated with the Composer webserver application.
63+
* @param {string} projectId The id for the project containing the Cloud Function.
64+
* @param {string} userAgent The user agent string which will be provided with the webserver request.
65+
*/
66+
function authorizeIap (clientId, projectId, userAgent) {
67+
const SERVICE_ACCOUNT = `${projectId}@appspot.gserviceaccount.com`;
68+
const JWT_HEADER = Buffer.from(JSON.stringify({alg: 'RS256', typ: 'JWT'}))
69+
.toString('base64');
70+
71+
var jwt = '';
72+
var jwtClaimset = '';
73+
74+
// Obtain an Oauth2 access token for the appspot service account
75+
return fetch(
76+
`http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/${SERVICE_ACCOUNT}/token`,
77+
{
78+
headers: {'User-Agent': userAgent, 'Metadata-Flavor': 'Google'}
79+
})
80+
.then(res => res.json())
81+
.then(function obtainAccessTokenCallback (tokenResponse) {
82+
var accessToken = tokenResponse.access_token;
83+
var iat = Math.floor(new Date().getTime() / 1000);
84+
var claims = {
85+
iss: SERVICE_ACCOUNT,
86+
aud: 'https://www.googleapis.com/oauth2/v4/token',
87+
iat: iat,
88+
exp: iat + 60,
89+
target_audience: clientId
90+
};
91+
jwtClaimset = Buffer.from(JSON.stringify(claims)).toString('base64');
92+
var toSign = [JWT_HEADER, jwtClaimset].join('.');
93+
94+
return fetch(
95+
`https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts/${SERVICE_ACCOUNT}:signBlob`,
96+
{
97+
method: 'POST',
98+
body: JSON.stringify({'bytesToSign': Buffer.from(toSign).toString('base64')}),
99+
headers: {
100+
'User-Agent': userAgent,
101+
'Authorization': `Bearer ${accessToken}`
102+
}
103+
});
104+
})
105+
.then(res => res.json())
106+
.then(function signJsonClaimCallback (body) {
107+
// Request service account signature on header and claimset
108+
var jwtSignature = body.signature;
109+
jwt = [JWT_HEADER, jwtClaimset, jwtSignature].join('.');
110+
var form = new FormData();
111+
form.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
112+
form.append('assertion', jwt);
113+
return fetch(
114+
'https://www.googleapis.com/oauth2/v4/token', {
115+
method: 'POST',
116+
body: form
117+
});
118+
})
119+
.then(res => res.json())
120+
.then(function returnJwt (body) {
121+
return {
122+
jwt: jwt,
123+
idToken: body.id_token
124+
};
125+
});
126+
}
127+
128+
/**
129+
* @param {string} url The url that the post request targets.
130+
* @param {string} body The body of the post request.
131+
* @param {string} idToken Bearer token used to authorize the iap request.
132+
* @param {string} userAgent The user agent to identify the requester.
133+
* @param {string} jwt A Json web token used to authenticate the request.
134+
*/
135+
function makeIapPostRequest (url, body, idToken, userAgent, jwt) {
136+
var form = new FormData();
137+
form.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
138+
form.append('assertion', jwt);
139+
140+
return fetch(
141+
url, {
142+
method: 'POST',
143+
body: form
144+
})
145+
.then(function makeIapPostRequestCallback () {
146+
return fetch(url, {
147+
method: 'POST',
148+
headers: {
149+
'User-Agent': userAgent,
150+
'Authorization': `Bearer ${idToken}`
151+
},
152+
body: JSON.stringify(body)
153+
});
154+
});
155+
}
156+
// [END composer_trigger]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "nodejs-docs-samples-functions-composer-storage-trigger",
3+
"version": "0.0.1",
4+
"dependencies": {
5+
"form-data": "^2.3.2",
6+
"node-fetch": "^2.2.0"
7+
},
8+
"engines": {
9+
"node": ">=4.3.2"
10+
},
11+
"private": true,
12+
"license": "Apache-2.0",
13+
"author": "Google Inc.",
14+
"repository": {
15+
"type": "git",
16+
"url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
17+
},
18+
"devDependencies": {
19+
"@google-cloud/nodejs-repo-tools": "^2.2.5"
20+
},
21+
"scripts": {
22+
"lint": "repo-tools lint"
23+
}
24+
}

0 commit comments

Comments
 (0)