-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathindex.js
187 lines (161 loc) · 6.53 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
const core = require('@actions/core');
const exec = require('@actions/exec');
// Run an external command.
// cwd: optional string
// check: whether to raise an exception if returnCode is non-zero. Defaults to true.
const run = async function (args, opts = {}) {
var result = {};
result.stdout = '';
var execOpts = {};
execOpts.listeners = {
stdout: (data) => {
result.stdout += data.toString();
},
};
execOpts.ignoreReturnCode = opts.check == false;
if ('cwd' in opts) {
execOpts.cwd = opts.cwd;
}
result.returnCode = await exec.exec(args[0], args.slice(1), execOpts);
return result;
}
// Returns array of submodules, where each item has properties: name, path, url
const getSubmodules = async function () {
const gitResult = await run(['git', 'config', '--file', '.gitmodules', '--list']);
// output looks like:
// submodule.aws-common-runtime/aws-c-common.path=crt/aws-c-common
// submodule.aws-common-runtime/aws-c-common.url=https://github.com/awslabs/aws-c-common.git
// ...
const re = /submodule\.(.+)\.(path|url)=(.+)/;
// build map with properties of each submodule
var map = {};
const lines = gitResult.stdout.split('\n');
for (var i = 0; i < lines.length; i++) {
const match = re.exec(lines[i]);
if (!match) {
continue;
}
const submoduleId = match[1];
const property = match[2];
const value = match[3];
let mapEntry = map[submoduleId] || {};
if (property === 'path') {
mapEntry.path = value;
// get "name" from final directory in path
mapEntry.name = value.split('/').pop()
} else if (property === 'url') {
mapEntry.url = value;
} else {
continue;
}
map[submoduleId] = mapEntry;
}
// return array, sorted by name
return Object.values(map).sort((a, b) => a.name.localeCompare(b.name));
}
// Diff the submodule against its state on origin/main.
// Returns null if they're the same.
// Otherwise returns something like {thisCommit: 'c74534c', mainCommit: 'b6656aa'}
const diffSubmodule = async function (submodule) {
const gitResult = await run(['git', 'diff', `origin/main`, '--', submodule.path]);
const stdout = gitResult.stdout;
// output looks like this:
//
// diff --git a/crt/aws-c-auth b/crt/aws-c-auth
// index b6656aa..c74534c 160000
// --- a/crt/aws-c-auth
// +++ b/crt/aws-c-auth
// @@ -1 +1 @@
// -Subproject commit b6656aad42edd5d11eea50936cb60359a6338e0b
// +Subproject commit c74534c13264868bbbd14b419c291580d3dd9141
try {
// let's just be naive and only look at the last 2 lines
// if this fails in any way, report no difference
var result = {}
result.mainCommit = stdout.match('\\-Subproject commit ([a-f0-9]{40})')[1];
result.thisCommit = stdout.match('\\+Subproject commit ([a-f0-9]{40})')[1];
return result;
} catch (error) {
return null;
}
}
// Returns whether one commit is an ancestor of another.
const isAncestor = async function (ancestor, descendant, cwd) {
const gitResult = await run(['git', 'merge-base', '--is-ancestor', ancestor, descendant], { check: false, cwd: cwd });
if (gitResult.returnCode == 0) {
return true;
}
if (gitResult.returnCode == 1) {
return false;
}
throw new Error(`The process 'git' failed with exit code ${gitResult.returnCode}`);
}
// Returns the release tag for a commit, or null if there is none
const getReleaseTag = async function (commit, cwd) {
const gitResult = await run(['git', 'describe', '--tags', '--exact-match', commit], { cwd: cwd, check: false });
if (gitResult.returnCode != 0) {
return null;
}
// ensure it's a properly formatted release tag
const match = gitResult.stdout.match(/^(v[0-9]+\.[0-9]+\.[0-9]+)$/m);
if (!match) {
return null;
}
return match[1];
}
const checkSubmodules = async function () {
const submodules = await getSubmodules();
for (var i = 0; i < submodules.length; i++) {
const submodule = submodules[i];
// Diff the submodule against its state on origin/main.
// If there's no difference, then there's no need to analyze further
const diff = await diffSubmodule(submodule);
if (diff == null) {
continue;
}
// Ensure submodule is at an acceptable commit:
// For repos the Common Runtime team controls, it must be at a tagged release.
// For other repos, where we can't just cut a release ourselves, it needs to at least be on the main branch.
const thisTag = await getReleaseTag(diff.thisCommit, submodule.path);
if (!thisTag) {
const nonCrtRepo = /^(aws-lc|s2n|s2n-tls)$/
if (nonCrtRepo.test(submodule.name)) {
const isOnMain = await isAncestor(diff.thisCommit, 'origin/main', submodule.path);
if (!isOnMain) {
if (/^(aws-lc)$/.test(submodule.name)) {
// for aws-lc, we may use a branch for FIPS support.
const isOnFIPS = await isAncestor(diff.thisCommit, 'origin/fips-2024-09-27', submodule.path);
if (!isOnFIPS) {
core.setFailed(`Submodule ${submodule.name} is using a branch`);
return;
}
} else {
core.setFailed(`Submodule ${submodule.name} is using a branch`);
return;
}
}
} else {
core.setFailed(`Submodule ${submodule.name} is not using a tagged release`);
return;
}
}
// prefer to use tags for further operations since they're easier to grok than commit hashes
const mainTag = await getReleaseTag(diff.mainCommit, submodule.path);
const thisCommit = thisTag || diff.thisCommit;
const mainCommit = mainTag || diff.mainCommit;
// freak out if our branch's submodule is older than where we're merging
if (await isAncestor(thisCommit, mainCommit, submodule.path)) {
core.setFailed(`Submodule ${submodule.name} is newer on origin/main:`
+ ` ${mainCommit} vs ${thisCommit} on this branch`);
return;
}
}
}
const main = async function () {
try {
await checkSubmodules();
} catch (error) {
core.setFailed(error.message);
}
}
main()