@ossy/deployment-tools 0.0.107 → 1.0.1
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/CHANGELOG.md +10 -2
- package/README.md +3 -7
- package/package.json +2 -2
- package/src/deploy/cli.js +0 -18
- package/src/deploy/platform-deployment.js +23 -16
- package/src/docker/docker-service.js +32 -11
- package/src/infrastructure/cli.js +5 -0
- package/src/infrastructure/container-deployment-target/container-deployment-target.js +10 -0
- package/src/infrastructure/ecr-stack.js +53 -0
- package/src/template/deployment-template.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## 1.0.1 (2026-04-05)
|
|
7
7
|
|
|
8
8
|
**Note:** Version bump only for package @ossy/deployment-tools
|
|
9
9
|
|
|
@@ -11,7 +11,15 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
## 0.0.
|
|
14
|
+
## 0.0.107 (2026-04-05)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @ossy/deployment-tools
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## 0.0.106 (2026-04-05)
|
|
15
23
|
|
|
16
24
|
**Note:** Version bump only for package @ossy/deployment-tools
|
|
17
25
|
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @ossy/deployment-tools
|
|
2
2
|
|
|
3
3
|
Collection of scripts and tools to aid deployment of
|
|
4
|
-
containers and static files to Amazon Web Services
|
|
4
|
+
containers and static files to Amazon Web Services (typically from CI).
|
|
5
5
|
|
|
6
6
|
## Server
|
|
7
7
|
|
|
@@ -30,12 +30,12 @@ The first argument after the package name is the **handler** (`deployment`). Com
|
|
|
30
30
|
|
|
31
31
|
Deployments are read from a **glob** of JSON files (e.g. `deployments.json`), and platform definitions from a **platforms** JSON file. Domain and platform for a given site are **not** taken from `ossy.json`; they live in those deployment records (and, in app workflows, mirror **`domain` / `platform` in `src/config.js`** when you use **`@ossy/cli publish`**).
|
|
32
32
|
|
|
33
|
+
**Container images must live in Amazon ECR.** Each `CONTAINER` row needs a **`registry`** field with your ECR API endpoint (for example `123456789012.dkr.ecr.eu-north-1.amazonaws.com`). Both the **CLI** (`deployment deploy`) and the **worker** (`server start`) require this. The worker **always** runs **`aws ecr get-login-password | docker login`** before **`docker pull`**, using the host IAM role (or environment AWS credentials). Do not put registry passwords on the queue.
|
|
34
|
+
|
|
33
35
|
### deploy (single domain)
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
38
|
npx --yes @ossy/deployment-tools deployment deploy \
|
|
37
|
-
--username "${{ github.actor }}" \
|
|
38
|
-
--authentication "${{ secrets.GITHUB_TOKEN }}" \
|
|
39
39
|
--domain example.com \
|
|
40
40
|
--platform my-platform \
|
|
41
41
|
--platforms-path packages/infrastructure/bin/deployment-platforms.json \
|
|
@@ -44,8 +44,6 @@ npx --yes @ossy/deployment-tools deployment deploy \
|
|
|
44
44
|
|
|
45
45
|
| Flag | Alias | Description |
|
|
46
46
|
|------|--------|-------------|
|
|
47
|
-
| `--username` | `-u` | User recorded on the deployment request |
|
|
48
|
-
| `--authentication` | `-a` | Token used to authorize the request |
|
|
49
47
|
| `--domain` | `-d` | Target site domain (must match an entry under that platform in the deployments glob) |
|
|
50
48
|
| `--platform` | `-p` | Target deployment platform name |
|
|
51
49
|
| `--platforms-path` | `-pp` | Path to platforms JSON (AWS / queue config) |
|
|
@@ -55,8 +53,6 @@ npx --yes @ossy/deployment-tools deployment deploy \
|
|
|
55
53
|
|
|
56
54
|
```bash
|
|
57
55
|
npx --yes @ossy/deployment-tools deployment deploy-all \
|
|
58
|
-
--username "${{ github.actor }}" \
|
|
59
|
-
--authentication "${{ secrets.GITHUB_TOKEN }}" \
|
|
60
56
|
--platform my-platform \
|
|
61
57
|
--platforms-path packages/infrastructure/bin/deployment-platforms.json \
|
|
62
58
|
--deployments-path "packages/infrastructure/deployments/**/*.json"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/deployment-tools",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Collection of scripts and tools to aid deployment of containers and static files to Amazon Web Services through GitHub Actions",
|
|
5
5
|
"source": "./src/index.js",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -31,5 +31,5 @@
|
|
|
31
31
|
"jest": "^27.5.1",
|
|
32
32
|
"jsdoc": "^4.0.2"
|
|
33
33
|
},
|
|
34
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "e5276fdb5dedf68b966d851077461b188c613241"
|
|
35
35
|
}
|
package/src/deploy/cli.js
CHANGED
|
@@ -7,12 +7,6 @@ const deploy = options => {
|
|
|
7
7
|
logInfo({ message: '[CLI] Running deploy command' })
|
|
8
8
|
|
|
9
9
|
const parsedArgs = arg({
|
|
10
|
-
'--username': String,
|
|
11
|
-
'-u': '--username',
|
|
12
|
-
|
|
13
|
-
'--authentication': String,
|
|
14
|
-
'-a': '--authentication',
|
|
15
|
-
|
|
16
10
|
'--domain': String,
|
|
17
11
|
'-d': '--domain',
|
|
18
12
|
|
|
@@ -25,12 +19,9 @@ const deploy = options => {
|
|
|
25
19
|
'--deployments-path': String,
|
|
26
20
|
'-dp': '--deployments-path',
|
|
27
21
|
|
|
28
|
-
|
|
29
22
|
}, { argv: options })
|
|
30
23
|
|
|
31
24
|
PlatformDeploymentService.deploy({
|
|
32
|
-
username: parsedArgs['--username'],
|
|
33
|
-
authentication: parsedArgs['--authentication'],
|
|
34
25
|
targetDomain: parsedArgs['--domain'],
|
|
35
26
|
targetPlatform: parsedArgs['--platform'],
|
|
36
27
|
pathToPlatformTemplates: parsedArgs['--platforms-path'],
|
|
@@ -42,12 +33,6 @@ const deployAll = options => {
|
|
|
42
33
|
logInfo({ message: '[CLI] Running deploy-all command' })
|
|
43
34
|
|
|
44
35
|
const parsedArgs = arg({
|
|
45
|
-
'--username': String,
|
|
46
|
-
'-u': '--username',
|
|
47
|
-
|
|
48
|
-
'--authentication': String,
|
|
49
|
-
'-a': '--authentication',
|
|
50
|
-
|
|
51
36
|
'--platform': String,
|
|
52
37
|
'-p': '--platform',
|
|
53
38
|
|
|
@@ -57,12 +42,9 @@ const deployAll = options => {
|
|
|
57
42
|
'--deployments-path': String,
|
|
58
43
|
'-dp': '--deployments-path',
|
|
59
44
|
|
|
60
|
-
|
|
61
45
|
}, { argv: options })
|
|
62
46
|
|
|
63
47
|
PlatformDeploymentService.deployAll({
|
|
64
|
-
username: parsedArgs['--username'],
|
|
65
|
-
authentication: parsedArgs['--authentication'],
|
|
66
48
|
targetPlatform: parsedArgs['--platform'],
|
|
67
49
|
pathToPlatformTemplates: parsedArgs['--platforms-path'],
|
|
68
50
|
pathToDeploymentTemplates: parsedArgs['--deployments-path']
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
const { PlatformTemplateService, DeploymentTemplateService } = require('../template')
|
|
2
2
|
const { PlatformConfigService, SupportedDeploymentTypes } = require('../config')
|
|
3
3
|
const { DeploymentQueueService } = require('../deployment-queue')
|
|
4
|
+
const { DockerService } = require('../docker/docker-service')
|
|
4
5
|
const { logError } = require('../log')
|
|
5
6
|
|
|
7
|
+
function assertEcrContainer(deploymentTemplate) {
|
|
8
|
+
const r = deploymentTemplate.registry
|
|
9
|
+
if (!r || !DockerService.isEcrRegistry(r)) {
|
|
10
|
+
logError({
|
|
11
|
+
message:
|
|
12
|
+
`[PlatformDeploymentService] ${deploymentTemplate.domain}: container deployments only support Amazon ECR. ` +
|
|
13
|
+
'Set `registry` in deployments JSON to your ECR hostname (e.g. 123456789012.dkr.ecr.eu-north-1.amazonaws.com).'
|
|
14
|
+
})
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
/**
|
|
7
21
|
* @class
|
|
8
22
|
*/
|
|
9
23
|
class PlatformDeploymentService {
|
|
10
24
|
|
|
11
25
|
static deploy({
|
|
12
|
-
username,
|
|
13
|
-
authentication,
|
|
14
26
|
targetDomain,
|
|
15
27
|
targetPlatform,
|
|
16
28
|
pathToPlatformTemplates,
|
|
@@ -41,13 +53,12 @@ class PlatformDeploymentService {
|
|
|
41
53
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
|
42
54
|
|
|
43
55
|
if (deploymentTemplate.type === SupportedDeploymentTypes.Container) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
...deploymentTemplate,
|
|
47
|
-
username: username,
|
|
48
|
-
authentication: authentication,
|
|
56
|
+
if (!assertEcrContainer(deploymentTemplate)) {
|
|
57
|
+
return Promise.reject()
|
|
49
58
|
}
|
|
50
59
|
|
|
60
|
+
const deploymentRequest = { ...deploymentTemplate }
|
|
61
|
+
|
|
51
62
|
return DeploymentQueueService.sendDeploymentRequest(platformConfig, deploymentRequest)
|
|
52
63
|
}
|
|
53
64
|
|
|
@@ -58,9 +69,6 @@ class PlatformDeploymentService {
|
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
static deployAll({
|
|
61
|
-
username,
|
|
62
|
-
authentication,
|
|
63
|
-
targetDomain,
|
|
64
72
|
targetPlatform,
|
|
65
73
|
pathToPlatformTemplates,
|
|
66
74
|
pathToDeploymentTemplates
|
|
@@ -82,7 +90,7 @@ class PlatformDeploymentService {
|
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
if (deploymentTemplatesForTargetPlatform.length === 0) {
|
|
85
|
-
logError({ message: `[PlatformDeploymentService] Could not find
|
|
93
|
+
logError({ message: `[PlatformDeploymentService] Could not find any deployment templates for platform ${targetPlatform}` })
|
|
86
94
|
return Promise.reject()
|
|
87
95
|
}
|
|
88
96
|
|
|
@@ -90,13 +98,12 @@ class PlatformDeploymentService {
|
|
|
90
98
|
|
|
91
99
|
return deploymentTemplatesForTargetPlatform.map(deploymentTemplate => {
|
|
92
100
|
if (deploymentTemplate.type === SupportedDeploymentTypes.Container) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
...deploymentTemplate,
|
|
96
|
-
username: username,
|
|
97
|
-
authentication: authentication,
|
|
101
|
+
if (!assertEcrContainer(deploymentTemplate)) {
|
|
102
|
+
return Promise.reject()
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
const deploymentRequest = { ...deploymentTemplate }
|
|
106
|
+
|
|
100
107
|
return DeploymentQueueService.sendDeploymentRequest(platformConfig, deploymentRequest)
|
|
101
108
|
}
|
|
102
109
|
|
|
@@ -15,6 +15,29 @@ const exec = command => new Promise((resolve, reject) => {
|
|
|
15
15
|
*/
|
|
16
16
|
class DockerService {
|
|
17
17
|
|
|
18
|
+
static isEcrRegistry(registry) {
|
|
19
|
+
return (
|
|
20
|
+
typeof registry === 'string' &&
|
|
21
|
+
registry.includes('.dkr.ecr.') &&
|
|
22
|
+
registry.includes('amazonaws.com')
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Log in to ECR using the host credentials (EC2 instance profile, env keys, etc.).
|
|
28
|
+
* Requires AWS CLI v2 on the worker (`aws ecr get-login-password`).
|
|
29
|
+
*/
|
|
30
|
+
static ecrLoginViaAwsCli(registry) {
|
|
31
|
+
const m =
|
|
32
|
+
typeof registry === 'string' &&
|
|
33
|
+
registry.match(/\.dkr\.ecr\.([a-z0-9-]+)\.amazonaws\.com/)
|
|
34
|
+
const region = (m && m[1]) || process.env.AWS_DEFAULT_REGION || 'eu-north-1'
|
|
35
|
+
logInfo({ message: `[DockerService] ECR login via IAM for ${registry} (region ${region})` })
|
|
36
|
+
return exec(
|
|
37
|
+
`bash -c 'aws ecr get-login-password --region ${region} | docker login --username AWS --password-stdin ${registry}'`
|
|
38
|
+
).catch(logErrorAndReject(`[DockerService] Could not IAM-login to ECR ${registry}`))
|
|
39
|
+
}
|
|
40
|
+
|
|
18
41
|
static startDefaultContainers(platformConfig) {
|
|
19
42
|
logInfo({ message: '[DockerService] Starting default containers' })
|
|
20
43
|
return exec(`docker run -d --name=mongodb --network=${platformConfig.ciDockerNetworkName} --network-alias=mongodb --rm mongo`)
|
|
@@ -72,17 +95,15 @@ class DockerService {
|
|
|
72
95
|
|
|
73
96
|
}
|
|
74
97
|
|
|
75
|
-
static resolveCredentials({ registry
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
: exec(`docker login ${registry} -u ${username} -p ${authentication}`)
|
|
85
|
-
.catch(logErrorAndReject(`[DockerService] Could not authenticate with ${registry}`))
|
|
98
|
+
static resolveCredentials({ registry }) {
|
|
99
|
+
if (!DockerService.isEcrRegistry(registry)) {
|
|
100
|
+
return Promise.reject(
|
|
101
|
+
new Error(
|
|
102
|
+
`[DockerService] Only Amazon ECR is supported; registry was: ${registry ?? '(missing)'}`
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
return DockerService.ecrLoginViaAwsCli(registry)
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
static deploy(platformConfig, deploymentRequest) {
|
|
@@ -5,6 +5,7 @@ const { TrustCiStack } = require('./trust-ci-stack')
|
|
|
5
5
|
const { DeploymentTargetStack } = require('./deployment-target-stack')
|
|
6
6
|
const { DnsStack } = require('./dns-stack')
|
|
7
7
|
const { SesStack } = require('./ses-stack')
|
|
8
|
+
const { EcrStack } = require('./ecr-stack')
|
|
8
9
|
const { PlatformTemplateService, DeploymentTemplateService } = require('../template')
|
|
9
10
|
const { PlatformConfigService } = require('../config')
|
|
10
11
|
|
|
@@ -28,6 +29,10 @@ Promise.all([
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
new TrustCiStack(app, `${config.platformName}-trust-ci`, stackProps)
|
|
32
|
+
new EcrStack(app, `${config.platformName}-ecr`, {
|
|
33
|
+
...stackProps,
|
|
34
|
+
deployments: deploymentMap[config.platformName] || []
|
|
35
|
+
})
|
|
31
36
|
|
|
32
37
|
const deploymentTarget = new DeploymentTargetStack(app, `${config.platformName}-deployment-target`, stackProps)
|
|
33
38
|
|
|
@@ -106,6 +106,16 @@ class ContainerDeploymentTarget extends Construct {
|
|
|
106
106
|
'route53:ListHostedZones'
|
|
107
107
|
],
|
|
108
108
|
resources: ['*']
|
|
109
|
+
}),
|
|
110
|
+
new PolicyStatement({
|
|
111
|
+
effect: Effect.ALLOW,
|
|
112
|
+
actions: [
|
|
113
|
+
'ecr:GetAuthorizationToken',
|
|
114
|
+
'ecr:BatchGetImage',
|
|
115
|
+
'ecr:GetDownloadUrlForLayer',
|
|
116
|
+
'ecr:BatchCheckLayerAvailability'
|
|
117
|
+
],
|
|
118
|
+
resources: ['*']
|
|
109
119
|
})
|
|
110
120
|
]
|
|
111
121
|
}))
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const { Stack, RemovalPolicy } = require('aws-cdk-lib')
|
|
2
|
+
const { Repository, TagStatus } = require('aws-cdk-lib/aws-ecr')
|
|
3
|
+
const { SupportedDeploymentTypes } = require('../config')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* EcrStackProps
|
|
7
|
+
* @typedef {Object} EcrStackProps
|
|
8
|
+
* @property {PlatformConfig} config - platform config
|
|
9
|
+
* @property {DeploymentTemplate[]} deployments - deployment templates
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates one ECR repository per unique container image name.
|
|
14
|
+
* @class
|
|
15
|
+
*/
|
|
16
|
+
class EcrStack extends Stack {
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} scope
|
|
20
|
+
* @param {string} id
|
|
21
|
+
* @param {EcrStackProps} props
|
|
22
|
+
*/
|
|
23
|
+
constructor(scope, id, props) {
|
|
24
|
+
super(scope, id, props)
|
|
25
|
+
|
|
26
|
+
const deployments = props.deployments || []
|
|
27
|
+
const repositoryNames = [...new Set(
|
|
28
|
+
deployments
|
|
29
|
+
.filter(deployment => deployment.type === SupportedDeploymentTypes.Container)
|
|
30
|
+
.map(deployment => deployment.image)
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
)]
|
|
33
|
+
|
|
34
|
+
repositoryNames.forEach(repositoryName => {
|
|
35
|
+
new Repository(this, `${repositoryName.replace(/[^A-Za-z0-9]/g, '-')}-repository`, {
|
|
36
|
+
repositoryName,
|
|
37
|
+
removalPolicy: RemovalPolicy.RETAIN,
|
|
38
|
+
imageScanOnPush: true,
|
|
39
|
+
lifecycleRules: [
|
|
40
|
+
{
|
|
41
|
+
description: 'Keep latest 5 tagged images',
|
|
42
|
+
maxImageCount: 5,
|
|
43
|
+
tagStatus: TagStatus.TAGGED
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
EcrStack
|
|
53
|
+
}
|