Skip to content

Commit eb520f0

Browse files
committed
refactor: extract inline cleanup script into shared JS file
1 parent 6657df8 commit eb520f0

3 files changed

Lines changed: 121 additions & 79 deletions

File tree

.github/actions/integration-tests/action.yml

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -39,43 +39,6 @@ runs:
3939
if: always()
4040
working-directory: integration-tests
4141
shell: bash
42-
run: |
43-
cds bind --exec -- node -e "
44-
const https = require('https');
45-
const vcap = JSON.parse(process.env.VCAP_SERVICES || '{}');
46-
const key = (vcap.aicore || vcap['ai-core'] || [{}])[0].credentials || JSON.parse(process.env.AICORE_SERVICE_KEY || 'null');
47-
if (!key) { console.log('No AI Core credentials found, skipping cleanup'); process.exit(0); }
48-
const tokenUrl = key.url + '/oauth/token';
49-
const apiUrl = key.serviceurls.AI_API_URL;
50-
function fetch(url, opts) {
51-
return new Promise((resolve, reject) => {
52-
const u = new URL(url);
53-
const req = https.request({hostname:u.hostname, path:u.pathname+u.search, method:opts.method||'GET', headers:opts.headers||{}}, res => {
54-
let data=''; res.on('data',c=>data+=c); res.on('end',()=>resolve({status:res.statusCode,body:data}));
55-
});
56-
req.on('error',reject);
57-
if(opts.body) req.write(opts.body);
58-
req.end();
59-
});
60-
}
61-
(async () => {
62-
const tokenParams = new URLSearchParams({grant_type:'client_credentials'});
63-
const authHeader = 'Basic ' + Buffer.from(key.clientid+':'+key.clientsecret).toString('base64');
64-
const tokenRes = await fetch(tokenUrl+'?'+tokenParams.toString(), {headers:{'Authorization':authHeader}});
65-
const token = JSON.parse(tokenRes.body).access_token;
66-
const headers = {'Authorization':'Bearer '+token, 'AI-Resource-Group':'default'};
67-
const runId = '${{ github.run_id }}';
68-
const attempt = '${{ github.run_attempt }}';
69-
const javaVersion = '${{ inputs.java-version }}';
70-
const ownPrefix = 'itest-' + runId + '-' + attempt + '-j' + javaVersion;
71-
const prefixes = [ownPrefix, 'itest-rg-', 'cds-itest-'];
72-
const rgRes = await fetch(apiUrl+'/v2/admin/resourceGroups', {headers});
73-
const groups = JSON.parse(rgRes.body).resources || [];
74-
const toDelete = groups.filter(rg => rg.resourceGroupId && prefixes.some(p => rg.resourceGroupId.startsWith(p)));
75-
for (const rg of toDelete) {
76-
const res = await fetch(apiUrl+'/v2/admin/resourceGroups/'+rg.resourceGroupId, {method:'DELETE', headers});
77-
console.log('Delete', rg.resourceGroupId, '->', res.status);
78-
}
79-
console.log('Cleaned up', toDelete.length, 'resource groups');
80-
})().catch(e => { console.error(e.message); process.exit(0); });
81-
" || true
42+
env:
43+
RESOURCE_GROUP_PREFIX: itest-${{ github.run_id }}-${{ github.run_attempt }}-j${{ inputs.java-version }}
44+
run: cds bind --exec -- node ${{ github.workspace }}/.github/scripts/cleanup-resource-groups.js || true

.github/actions/scan-with-sonar/action.yml

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -52,45 +52,9 @@ runs:
5252
if: always()
5353
working-directory: integration-tests
5454
shell: bash
55-
run: |
56-
cds bind --exec -- node -e "
57-
const https = require('https');
58-
const vcap = JSON.parse(process.env.VCAP_SERVICES || '{}');
59-
const key = (vcap.aicore || vcap['ai-core'] || [{}])[0].credentials || JSON.parse(process.env.AICORE_SERVICE_KEY || 'null');
60-
if (!key) { console.log('No AI Core credentials found, skipping cleanup'); process.exit(0); }
61-
const tokenUrl = key.url + '/oauth/token';
62-
const apiUrl = key.serviceurls.AI_API_URL;
63-
function fetch(url, opts) {
64-
return new Promise((resolve, reject) => {
65-
const u = new URL(url);
66-
const req = https.request({hostname:u.hostname, path:u.pathname+u.search, method:opts.method||'GET', headers:opts.headers||{}}, res => {
67-
let data=''; res.on('data',c=>data+=c); res.on('end',()=>resolve({status:res.statusCode,body:data}));
68-
});
69-
req.on('error',reject);
70-
if(opts.body) req.write(opts.body);
71-
req.end();
72-
});
73-
}
74-
(async () => {
75-
const tokenParams = new URLSearchParams({grant_type:'client_credentials'});
76-
const authHeader = 'Basic ' + Buffer.from(key.clientid+':'+key.clientsecret).toString('base64');
77-
const tokenRes = await fetch(tokenUrl+'?'+tokenParams.toString(), {headers:{'Authorization':authHeader}});
78-
const token = JSON.parse(tokenRes.body).access_token;
79-
const headers = {'Authorization':'Bearer '+token, 'AI-Resource-Group':'default'};
80-
const runId = '${{ github.run_id }}';
81-
const attempt = '${{ github.run_attempt }}';
82-
const ownPrefix = 'sonar-' + runId + '-' + attempt;
83-
const prefixes = [ownPrefix, 'itest-rg-', 'cds-itest-'];
84-
const rgRes = await fetch(apiUrl+'/v2/admin/resourceGroups', {headers});
85-
const groups = JSON.parse(rgRes.body).resources || [];
86-
const toDelete = groups.filter(rg => rg.resourceGroupId && prefixes.some(p => rg.resourceGroupId.startsWith(p)));
87-
for (const rg of toDelete) {
88-
const res = await fetch(apiUrl+'/v2/admin/resourceGroups/'+rg.resourceGroupId, {method:'DELETE', headers});
89-
console.log('Delete', rg.resourceGroupId, '->', res.status);
90-
}
91-
console.log('Cleaned up', toDelete.length, 'resource groups');
92-
})().catch(e => { console.error(e.message); process.exit(0); });
93-
" || true
55+
env:
56+
RESOURCE_GROUP_PREFIX: sonar-${{ github.run_id }}-${{ github.run_attempt }}
57+
run: cds bind --exec -- node ${{ github.workspace }}/.github/scripts/cleanup-resource-groups.js || true
9458

9559
- name: Generate aggregate coverage report
9660
run: mvn verify -ntp -B -pl coverage-report -am -DskipTests
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* Cleans up AI Core test resource groups.
3+
*
4+
* Required environment variables:
5+
* RESOURCE_GROUP_PREFIX - The prefix identifying resource groups owned by this run
6+
* (e.g. "itest-12345-1-j17" or "sonar-12345-1")
7+
*
8+
* Optional environment variables:
9+
* STALE_PREFIXES - Comma-separated list of additional prefixes to clean up
10+
* (defaults to "itest-rg-,cds-itest-")
11+
*
12+
* Credentials are resolved from VCAP_SERVICES (via cds bind) or AICORE_SERVICE_KEY.
13+
*/
14+
const https = require("https");
15+
16+
const DEFAULT_STALE_PREFIXES = ["itest-rg-", "cds-itest-"];
17+
18+
function getCredentials() {
19+
const vcap = JSON.parse(process.env.VCAP_SERVICES || "{}");
20+
return (
21+
(vcap.aicore || vcap["ai-core"] || [{}])[0].credentials ||
22+
JSON.parse(process.env.AICORE_SERVICE_KEY || "null")
23+
);
24+
}
25+
26+
function request(url, opts = {}) {
27+
return new Promise((resolve, reject) => {
28+
const u = new URL(url);
29+
const req = https.request(
30+
{
31+
hostname: u.hostname,
32+
path: u.pathname + u.search,
33+
method: opts.method || "GET",
34+
headers: opts.headers || {},
35+
},
36+
(res) => {
37+
let data = "";
38+
res.on("data", (chunk) => (data += chunk));
39+
res.on("end", () => resolve({ status: res.statusCode, body: data }));
40+
}
41+
);
42+
req.on("error", reject);
43+
if (opts.body) req.write(opts.body);
44+
req.end();
45+
});
46+
}
47+
48+
async function getAccessToken(credentials) {
49+
const tokenUrl = credentials.url + "/oauth/token";
50+
const params = new URLSearchParams({ grant_type: "client_credentials" });
51+
const authHeader =
52+
"Basic " +
53+
Buffer.from(credentials.clientid + ":" + credentials.clientsecret).toString(
54+
"base64"
55+
);
56+
const res = await request(tokenUrl + "?" + params.toString(), {
57+
headers: { Authorization: authHeader },
58+
});
59+
return JSON.parse(res.body).access_token;
60+
}
61+
62+
async function deleteResourceGroups(apiUrl, headers, prefixes) {
63+
const res = await request(apiUrl + "/v2/admin/resourceGroups", { headers });
64+
const groups = JSON.parse(res.body).resources || [];
65+
const toDelete = groups.filter(
66+
(rg) =>
67+
rg.resourceGroupId &&
68+
prefixes.some((p) => rg.resourceGroupId.startsWith(p))
69+
);
70+
71+
for (const rg of toDelete) {
72+
const delRes = await request(
73+
apiUrl + "/v2/admin/resourceGroups/" + rg.resourceGroupId,
74+
{ method: "DELETE", headers }
75+
);
76+
console.log("Delete", rg.resourceGroupId, "->", delRes.status);
77+
}
78+
79+
console.log("Cleaned up", toDelete.length, "resource groups");
80+
}
81+
82+
async function main() {
83+
const ownPrefix = process.env.RESOURCE_GROUP_PREFIX;
84+
if (!ownPrefix) {
85+
console.error("RESOURCE_GROUP_PREFIX environment variable is required");
86+
process.exit(1);
87+
}
88+
89+
const credentials = getCredentials();
90+
if (!credentials) {
91+
console.log("No AI Core credentials found, skipping cleanup");
92+
return;
93+
}
94+
95+
const stalePrefixes = process.env.STALE_PREFIXES
96+
? process.env.STALE_PREFIXES.split(",").map((s) => s.trim())
97+
: DEFAULT_STALE_PREFIXES;
98+
99+
const prefixes = [ownPrefix, ...stalePrefixes];
100+
101+
const apiUrl = credentials.serviceurls.AI_API_URL;
102+
const token = await getAccessToken(credentials);
103+
const headers = {
104+
Authorization: "Bearer " + token,
105+
"AI-Resource-Group": "default",
106+
};
107+
108+
console.log("Cleaning resource groups matching prefixes:", prefixes);
109+
await deleteResourceGroups(apiUrl, headers, prefixes);
110+
}
111+
112+
main().catch((e) => {
113+
console.error(e.message);
114+
process.exit(0);
115+
});

0 commit comments

Comments
 (0)