@bifravst/ci 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +22 -0
- package/dist/CIApp.d.ts +5 -0
- package/dist/CIApp.js +8 -0
- package/dist/CIStack.d.ts +8 -0
- package/dist/CIStack.js +18 -0
- package/dist/RepoPermission.d.ts +10 -0
- package/dist/RepoPermission.js +26 -0
- package/dist/ci.d.ts +1 -0
- package/dist/ci.js +20 -0
- package/dist/ensureGitHubOIDCProvider.d.ts +7 -0
- package/dist/ensureGitHubOIDCProvider.js +37 -0
- package/dist/listRepos.d.ts +7 -0
- package/dist/listRepos.js +17 -0
- package/dist/loadRepoList.d.ts +2 -0
- package/dist/loadRepoList.js +9 -0
- package/dist/loadRepoList.spec.d.ts +1 -0
- package/dist/loadRepoList.spec.js +18 -0
- package/dist/sanitize.d.ts +1 -0
- package/dist/sanitize.js +1 -0
- package/package.json +101 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2024, Nordic Semiconductor ASA | nordicsemi.no
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# `hello.nrfcloud.com` CI [](https://www.npmjs.com/package/@bifravst/ci)
|
|
2
|
+
|
|
3
|
+
[](https://github.com/bifravst/ci/actions/workflows/test-and-release.yaml)
|
|
4
|
+
[](https://github.com/semantic-release/semantic-release)
|
|
5
|
+
[](https://renovatebot.com)
|
|
6
|
+
[](https://github.com/conventional-changelog/commitlint/tree/master/@commitlint/config-conventional)
|
|
7
|
+
[](https://github.com/prettier/prettier/)
|
|
8
|
+
[](https://github.com/typescript-eslint/typescript-eslint)
|
|
9
|
+
|
|
10
|
+
Sets up the permissions in our AWS CI account for the repositories in this
|
|
11
|
+
GitHub organization that are supposed to have access so they are be able to use
|
|
12
|
+
it for CI runs.
|
|
13
|
+
|
|
14
|
+
The allowed list of repositories is managed via the [`repos.txt`](./repos.txt)
|
|
15
|
+
file.
|
|
16
|
+
|
|
17
|
+
> [!CAUTION]
|
|
18
|
+
> Do not run this against the production account, but against the CI account.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx cdk deploy
|
|
22
|
+
```
|
package/dist/CIApp.d.ts
ADDED
package/dist/CIApp.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { App, Stack } from 'aws-cdk-lib';
|
|
2
|
+
import type { Repos } from './listRepos.js';
|
|
3
|
+
export declare class CIStack extends Stack {
|
|
4
|
+
constructor(parent: App, name: string, { repos, gitHubOICDProviderArn, }: {
|
|
5
|
+
gitHubOICDProviderArn: string;
|
|
6
|
+
repos: Repos;
|
|
7
|
+
});
|
|
8
|
+
}
|
package/dist/CIStack.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { App, CfnOutput, Stack } from 'aws-cdk-lib';
|
|
2
|
+
import { RepoPermission } from './RepoPermission.js';
|
|
3
|
+
export class CIStack extends Stack {
|
|
4
|
+
constructor(parent, name, { repos, gitHubOICDProviderArn, }) {
|
|
5
|
+
super(parent, name);
|
|
6
|
+
for (const { id, owner, name: repo } of repos) {
|
|
7
|
+
const perm = new RepoPermission(this, repo, {
|
|
8
|
+
repository: { id, owner, name: repo },
|
|
9
|
+
gitHubOICDProviderArn,
|
|
10
|
+
});
|
|
11
|
+
new CfnOutput(this, `${id}:ciRoleArn`, {
|
|
12
|
+
exportName: `${name}:${id}:ciRoleArn`,
|
|
13
|
+
description: `Role ARN to use for running continuous integration tests using GitHub Actions in the repository ${owner}/${repo} (${id})`,
|
|
14
|
+
value: perm.role.roleArn,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { aws_iam as IAM } from 'aws-cdk-lib';
|
|
2
|
+
import { Construct } from 'constructs';
|
|
3
|
+
import type { Repository } from './listRepos.js';
|
|
4
|
+
export declare class RepoPermission extends Construct {
|
|
5
|
+
readonly role: IAM.IRole;
|
|
6
|
+
constructor(parent: Construct, id: string, { repository, gitHubOICDProviderArn, }: {
|
|
7
|
+
repository: Repository;
|
|
8
|
+
gitHubOICDProviderArn: string;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Duration, aws_iam as IAM, Stack } from 'aws-cdk-lib';
|
|
2
|
+
import { Construct } from 'constructs';
|
|
3
|
+
import { sanitize } from './sanitize.js';
|
|
4
|
+
export class RepoPermission extends Construct {
|
|
5
|
+
role;
|
|
6
|
+
constructor(parent, id, { repository, gitHubOICDProviderArn, }) {
|
|
7
|
+
super(parent, id);
|
|
8
|
+
const gitHubOIDC = IAM.OpenIdConnectProvider.fromOpenIdConnectProviderArn(this, 'gitHubOICDProvider', gitHubOICDProviderArn);
|
|
9
|
+
this.role = new IAM.Role(this, 'ghRole', {
|
|
10
|
+
roleName: `${Stack.of(this).stackName}-${sanitize(repository.name)}`,
|
|
11
|
+
assumedBy: new IAM.WebIdentityPrincipal(gitHubOIDC.openIdConnectProviderArn, {
|
|
12
|
+
StringEquals: {
|
|
13
|
+
[`token.actions.githubusercontent.com:aud`]: 'sts.amazonaws.com',
|
|
14
|
+
},
|
|
15
|
+
StringLike: {
|
|
16
|
+
[`token.actions.githubusercontent.com:sub`]: `repo:${repository.owner}/${repository.name}:*`,
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
description: `This role is used by GitHub Actions to run Continuous Integration Tests ${Stack.of(this).stackName} in the repository ${repository.owner}/${repository.name} (${repository.id}).`,
|
|
20
|
+
maxSessionDuration: Duration.hours(1),
|
|
21
|
+
managedPolicies: [
|
|
22
|
+
IAM.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'),
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/ci.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/ci.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IAMClient } from '@aws-sdk/client-iam';
|
|
2
|
+
import { ensureGitHubOIDCProvider } from './ensureGitHubOIDCProvider.js';
|
|
3
|
+
import { fromEnv } from '@nordicsemiconductor/from-env';
|
|
4
|
+
import { CIApp } from './CIApp.js';
|
|
5
|
+
import { listRepos } from './listRepos.js';
|
|
6
|
+
import { loadRepoList } from './loadRepoList.js';
|
|
7
|
+
const { token } = fromEnv({
|
|
8
|
+
token: 'GITHUB_TOKEN',
|
|
9
|
+
})(process.env);
|
|
10
|
+
const repos = await listRepos(token, await loadRepoList(process.env.REPOS_LIST ?? './repos.txt'));
|
|
11
|
+
for (const repo of repos) {
|
|
12
|
+
console.debug(`Setting up permissions for ${repo.name} (${repo.id})...`);
|
|
13
|
+
}
|
|
14
|
+
const iam = new IAMClient({});
|
|
15
|
+
new CIApp(process.env.STACK_NAME ?? 'bifravst-ci', {
|
|
16
|
+
gitHubOICDProviderArn: await ensureGitHubOIDCProvider({
|
|
17
|
+
iam,
|
|
18
|
+
}),
|
|
19
|
+
repos,
|
|
20
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CreateOpenIDConnectProviderCommand, GetOpenIDConnectProviderCommand, IAMClient, ListOpenIDConnectProvidersCommand, } from '@aws-sdk/client-iam';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the ARN of the OpenID Connect provider for GitHub of the account.
|
|
5
|
+
*/
|
|
6
|
+
export const ensureGitHubOIDCProvider = async ({ iam, }) => {
|
|
7
|
+
const { OpenIDConnectProviderList } = await iam.send(new ListOpenIDConnectProvidersCommand({}));
|
|
8
|
+
const maybeGithubProvider = (await Promise.all(OpenIDConnectProviderList?.map(async ({ Arn }) => iam
|
|
9
|
+
.send(new GetOpenIDConnectProviderCommand({
|
|
10
|
+
OpenIDConnectProviderArn: Arn,
|
|
11
|
+
}))
|
|
12
|
+
.then((provider) => ({ Arn, provider }))) ?? [])).find(({ provider: { Url } }) => Url === 'token.actions.githubusercontent.com');
|
|
13
|
+
if (maybeGithubProvider?.Arn !== undefined) {
|
|
14
|
+
console.debug(chalk.green(`OIDC provider for GitHub exists: ${maybeGithubProvider.Arn}`));
|
|
15
|
+
return maybeGithubProvider.Arn;
|
|
16
|
+
}
|
|
17
|
+
console.log(chalk.yellow(`OIDC provider for GitHub does not exist. Creating ...`));
|
|
18
|
+
const provider = await iam.send(new CreateOpenIDConnectProviderCommand({
|
|
19
|
+
Url: `https://token.actions.githubusercontent.com`,
|
|
20
|
+
ClientIDList: ['sts.amazonaws.com'],
|
|
21
|
+
/**
|
|
22
|
+
* Starting July 6, 2023, AWS began securing communication with
|
|
23
|
+
* GitHub’s OIDC identity provider (IdP) using our library of
|
|
24
|
+
* trusted root Certificate Authorities instead of using a
|
|
25
|
+
* certificate thumbprint to verify the IdP’s server certificate.
|
|
26
|
+
* This approach ensures that your GitHub OIDC configuration behaves
|
|
27
|
+
* correctly without disruption during future certificate rotations
|
|
28
|
+
* and changes. With this new validation approach in place, your
|
|
29
|
+
* legacy thumbprint(s) will remain in your configuration but will
|
|
30
|
+
* no longer be needed for validation purposes.
|
|
31
|
+
*/
|
|
32
|
+
ThumbprintList: ['6938fd4d98bab03faadb97b34396831e3780aea1'],
|
|
33
|
+
}));
|
|
34
|
+
if (provider.OpenIDConnectProviderArn === undefined)
|
|
35
|
+
throw new Error(`Failed to create OpenID Connect Provider for GitHub!`);
|
|
36
|
+
return provider.OpenIDConnectProviderArn;
|
|
37
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Octokit } from '@octokit/rest';
|
|
2
|
+
export const listRepos = async (token, repos) => {
|
|
3
|
+
const client = new Octokit({
|
|
4
|
+
auth: token,
|
|
5
|
+
});
|
|
6
|
+
return (await Promise.all(repos.map(async (repo) => client.repos.get({
|
|
7
|
+
repo: repo.name,
|
|
8
|
+
owner: repo.owner,
|
|
9
|
+
}))))
|
|
10
|
+
.map((res) => res.data)
|
|
11
|
+
.filter((repo) => repo.archived !== true)
|
|
12
|
+
.map((repo) => ({
|
|
13
|
+
id: repo.id,
|
|
14
|
+
name: repo.name,
|
|
15
|
+
owner: repo.full_name.split('/')[0],
|
|
16
|
+
}));
|
|
17
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
export const loadRepoList = async (location) => (await readFile(location, 'utf-8'))
|
|
3
|
+
.trim()
|
|
4
|
+
.split('\n')
|
|
5
|
+
.filter((s) => !s.startsWith('#'))
|
|
6
|
+
.map((s) => {
|
|
7
|
+
const [owner, name] = s.split('/');
|
|
8
|
+
return { owner, name };
|
|
9
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { loadRepoList } from './loadRepoList.js';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
void describe('loadRepoList()', () => {
|
|
6
|
+
void it('should load the list of repos', async () => {
|
|
7
|
+
assert.deepEqual(await loadRepoList(path.join(process.cwd(), 'cdk', 'test', 'repos.txt')), [
|
|
8
|
+
{
|
|
9
|
+
name: 'ci',
|
|
10
|
+
owner: 'bifravst',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'aws-cdk-ecr-helpers',
|
|
14
|
+
owner: 'bifravst',
|
|
15
|
+
},
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sanitize: (s: string) => string;
|
package/dist/sanitize.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const sanitize = (s) => s.replace(/[^a-z0-9-]/g, '');
|
package/package.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bifravst/ci",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Sets up the permissions in our CI account for all repositories in this account to be able to use it for CI runs.",
|
|
5
|
+
"exports": {
|
|
6
|
+
"./*": {
|
|
7
|
+
"import": {
|
|
8
|
+
"types": "./dist/*.d.ts",
|
|
9
|
+
"default": "./dist/*.js"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"prepare": "husky && check-node-version --package",
|
|
16
|
+
"test": "npx globstar -- npx tsx --test --test-reporter spec \"!(node_modules)/**/*.spec.ts\"",
|
|
17
|
+
"prepublishOnly": "npx tsc --noEmit false --outDir ./dist -d"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/bifravst/ci.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/bifravst/ci/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/bifravst/ci#readme",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"nordicsemiconductor",
|
|
29
|
+
"cellular-iot",
|
|
30
|
+
"bifravst",
|
|
31
|
+
"aws",
|
|
32
|
+
"cdk"
|
|
33
|
+
],
|
|
34
|
+
"author": "Nordic Semiconductor ASA | nordicsemi.no",
|
|
35
|
+
"license": "BSD-3-Clause",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@bifravst/eslint-config-typescript": "6.0.19",
|
|
38
|
+
"@bifravst/prettier-config": "1.0.0",
|
|
39
|
+
"@commitlint/config-conventional": "19.1.0",
|
|
40
|
+
"@nordicsemiconductor/from-env": "3.0.1",
|
|
41
|
+
"@octokit/rest": "20.1.0",
|
|
42
|
+
"aws-cdk": "2.135.0",
|
|
43
|
+
"aws-cdk-lib": "2.135.0",
|
|
44
|
+
"cdk": "2.135.0",
|
|
45
|
+
"check-node-version": "4.2.1",
|
|
46
|
+
"commitlint": "19.2.1",
|
|
47
|
+
"constructs": "10.3.0",
|
|
48
|
+
"globstar": "1.0.0",
|
|
49
|
+
"husky": "9.0.11",
|
|
50
|
+
"tsx": "4.7.2"
|
|
51
|
+
},
|
|
52
|
+
"lint-staged": {
|
|
53
|
+
"*.{md,json,yaml,yml}": [
|
|
54
|
+
"prettier --write"
|
|
55
|
+
],
|
|
56
|
+
"*.{ts,tsx}": [
|
|
57
|
+
"prettier --write",
|
|
58
|
+
"eslint"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=20",
|
|
63
|
+
"npm": ">=9"
|
|
64
|
+
},
|
|
65
|
+
"release": {
|
|
66
|
+
"branches": [
|
|
67
|
+
"saga",
|
|
68
|
+
{
|
|
69
|
+
"name": "!(saga)",
|
|
70
|
+
"prerelease": true
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"remoteTags": true,
|
|
74
|
+
"plugins": [
|
|
75
|
+
"@semantic-release/commit-analyzer",
|
|
76
|
+
"@semantic-release/release-notes-generator",
|
|
77
|
+
"@semantic-release/npm",
|
|
78
|
+
[
|
|
79
|
+
"@semantic-release/github",
|
|
80
|
+
{
|
|
81
|
+
"successComment": false,
|
|
82
|
+
"failTitle": false
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"publishConfig": {
|
|
88
|
+
"access": "public"
|
|
89
|
+
},
|
|
90
|
+
"files": [
|
|
91
|
+
"package-lock.json",
|
|
92
|
+
"dist",
|
|
93
|
+
"LICENSE",
|
|
94
|
+
"README.md"
|
|
95
|
+
],
|
|
96
|
+
"prettier": "@bifravst/prettier-config",
|
|
97
|
+
"peerDependencies": {
|
|
98
|
+
"@aws-sdk/client-iam": "^3.549.0",
|
|
99
|
+
"chalk": "^5.3.0"
|
|
100
|
+
}
|
|
101
|
+
}
|