@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 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
- ## 0.0.107 (2026-03-29)
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.106 (2026-03-29)
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 through GitHub Actions.
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": "0.0.107",
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": "280ce1f62d7daf721909ac5eabe4181991e00456"
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
- const deploymentRequest = {
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 a deployment template for ${targetDomain} in ${targetPlatform}` })
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
- const deploymentRequest = {
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, username, authentication }) {
76
- const shouldAuthenticate = username || authentication
77
-
78
- shouldAuthenticate
79
- ? logInfo({ message: '[DockerService] Docker credentials provided, logging in to private repository' })
80
- : logInfo({ message: '[DockerService] No docker credentials provided, assuming image is publicly hosted' })
81
-
82
- return !shouldAuthenticate
83
- ? Promise.resolve()
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
+ }
@@ -33,7 +33,6 @@ class DeploymentTemplateService {
33
33
  .flatMap(json => JSON.parse(json))
34
34
  .map(deploymentTemplate => ({
35
35
  containerPort: '3000',
36
- registry: 'ghcr.io',
37
36
  ...deploymentTemplate
38
37
  }))
39
38
  .reduce((deploymentsMap, deployment) => {