@ossy/deployment-tools 1.19.0 → 1.20.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.
@@ -0,0 +1,163 @@
1
+ /**
2
+ * @typedef {'http'|'tcp'} ServiceType
3
+ *
4
+ * @typedef {Object} HttpServiceConfig
5
+ * @property {'http'} [type] - defaults to 'http'
6
+ * @property {string} name - unique name, used for container name and systemd unit
7
+ * @property {string} domain - domain this service handles (Caddy block + Route53)
8
+ * @property {string} image - Docker image to run
9
+ *
10
+ * @typedef {Object} TcpServiceConfig
11
+ * @property {'tcp'} type - marks this as a raw TCP/UDP service (no HTTP/Caddy)
12
+ * @property {string} name - unique name, used for container name and systemd unit
13
+ * @property {string} image - Docker image to run
14
+ * @property {number[]} ports - ports to expose directly (same inside and outside container)
15
+ * @property {'tcp'|'udp'|'both'} [protocol] - defaults to 'tcp'
16
+ *
17
+ * @typedef {HttpServiceConfig|TcpServiceConfig} ContainerServiceConfig
18
+ */
19
+
20
+ const BASE_HTTP_PORT = 3002 // 3000 = platform runtime, 3001 = API
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // HTTP service — reverse-proxied through Caddy, container listens on port 3000
24
+ // ---------------------------------------------------------------------------
25
+
26
+ class HttpContainerService {
27
+ constructor({ name, domain, image }, httpIndex) {
28
+ this.name = name
29
+ this.domain = domain
30
+ this.image = image
31
+ this.hostPort = BASE_HTTP_PORT + httpIndex
32
+ }
33
+
34
+ get systemdUnitFile() {
35
+ return `
36
+ [Unit]
37
+ Description=Container service: ${this.name} (${this.domain})
38
+ After=network.target docker.service
39
+ Requires=docker.service
40
+
41
+ [Service]
42
+ EnvironmentFile=/etc/environment
43
+ ExecStartPre=-/usr/bin/docker rm -f ${this.name}
44
+ ExecStartPre=/usr/bin/docker pull ${this.image}
45
+ ExecStart=/usr/bin/docker run --name ${this.name} \\
46
+ --network ossy-network \\
47
+ -p ${this.hostPort}:3000 \\
48
+ --env-file /etc/environment \\
49
+ ${this.image}
50
+ ExecStop=/usr/bin/docker stop ${this.name}
51
+ Restart=on-failure
52
+ RestartSec=5
53
+
54
+ [Install]
55
+ WantedBy=multi-user.target
56
+ `
57
+ }
58
+
59
+ get caddyBlock() {
60
+ return `
61
+ ${this.domain} {
62
+ tls {
63
+ dns route53 {
64
+ max_retries 10
65
+ profile ci-client
66
+ }
67
+ }
68
+ reverse_proxy localhost:${this.hostPort}
69
+ }`
70
+ }
71
+
72
+ /** Ports to open in the EC2 security group. HTTP services use Caddy on 80/443 — no extra rules needed. */
73
+ get securityGroupPorts() {
74
+ return []
75
+ }
76
+
77
+ get unitName() { return `${this.name}.service` }
78
+ install() { return [`sudo tee /etc/systemd/system/${this.unitName} > /dev/null << 'EOF'\n${this.systemdUnitFile}\nEOF`] }
79
+ enable() { return [`sudo systemctl enable ${this.name}`] }
80
+ start() { return [`sudo systemctl start ${this.name}`] }
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // TCP/UDP service — bypasses Caddy, ports exposed directly on the host.
85
+ // Used for game servers and any other raw socket services.
86
+ // ---------------------------------------------------------------------------
87
+
88
+ class TcpContainerService {
89
+ constructor({ name, image, ports, protocol = 'tcp' }) {
90
+ this.name = name
91
+ this.image = image
92
+ this.ports = ports
93
+ this.protocol = protocol // 'tcp' | 'udp' | 'both'
94
+ }
95
+
96
+ get systemdUnitFile() {
97
+ const portMappings = this.ports.map(p => `-p ${p}:${p}`).join(' \\\n ')
98
+ return `
99
+ [Unit]
100
+ Description=Container service: ${this.name} (${this.protocol.toUpperCase()} ${this.ports.join(', ')})
101
+ After=network.target docker.service
102
+ Requires=docker.service
103
+
104
+ [Service]
105
+ EnvironmentFile=/etc/environment
106
+ ExecStartPre=-/usr/bin/docker rm -f ${this.name}
107
+ ExecStartPre=/usr/bin/docker pull ${this.image}
108
+ ExecStart=/usr/bin/docker run --name ${this.name} \\
109
+ --network ossy-network \\
110
+ ${portMappings} \\
111
+ --env-file /etc/environment \\
112
+ ${this.image}
113
+ ExecStop=/usr/bin/docker stop ${this.name}
114
+ Restart=on-failure
115
+ RestartSec=5
116
+
117
+ [Install]
118
+ WantedBy=multi-user.target
119
+ `
120
+ }
121
+
122
+ /** TCP services bypass Caddy — no Caddy block. */
123
+ get caddyBlock() { return null }
124
+
125
+ /** Each port needs an explicit security group ingress rule. */
126
+ get securityGroupPorts() {
127
+ return this.ports.flatMap(port => {
128
+ if (this.protocol === 'both') return [{ port, protocol: 'tcp' }, { port, protocol: 'udp' }]
129
+ return [{ port, protocol: this.protocol }]
130
+ })
131
+ }
132
+
133
+ get unitName() { return `${this.name}.service` }
134
+ install() { return [`sudo tee /etc/systemd/system/${this.unitName} > /dev/null << 'EOF'\n${this.systemdUnitFile}\nEOF`] }
135
+ enable() { return [`sudo systemctl enable ${this.name}`] }
136
+ start() { return [`sudo systemctl start ${this.name}`] }
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // Factory
141
+ // ---------------------------------------------------------------------------
142
+
143
+ /**
144
+ * Build service instances from the `services` array in platforms.json.
145
+ * HTTP services auto-assign host ports starting at 3002 (counted separately
146
+ * from TCP services so reordering TCP entries doesn't shift HTTP ports).
147
+ *
148
+ * @param {ContainerServiceConfig[]} services
149
+ * @returns {(HttpContainerService|TcpContainerService)[]}
150
+ */
151
+ const fromConfig = (services = []) => {
152
+ let httpIndex = 0
153
+ return services.map(s => {
154
+ if (s.type === 'tcp') return new TcpContainerService(s)
155
+ return new HttpContainerService(s, httpIndex++)
156
+ })
157
+ }
158
+
159
+ module.exports = {
160
+ HttpContainerService,
161
+ TcpContainerService,
162
+ fromConfig
163
+ }
@@ -1,97 +1,50 @@
1
1
  /* eslint-disable no-new */
2
- const { CfnOutput, Stack, RemovalPolicy } = require('aws-cdk-lib')
3
- const { BackupPlan, BackupVault, BackupResource } = require('aws-cdk-lib/aws-backup')
4
- const { Bucket, BlockPublicAccess } = require('aws-cdk-lib/aws-s3')
5
- const { Distribution, PriceClass, ResponseHeadersPolicy } = require('aws-cdk-lib/aws-cloudfront')
6
- const { S3BucketOrigin } = require('aws-cdk-lib/aws-cloudfront-origins')
2
+ const { CfnOutput, Stack } = require('aws-cdk-lib')
7
3
  const { ContainerDeploymentTarget } = require('./container-deployment-target')
8
4
 
9
5
  /**
10
- * @class
11
- */
6
+ * DeploymentTargetStackProps
7
+ * @typedef {Object} DeploymentTargetStackProps
8
+ * @property {PlatformConfig} config - platform config
9
+ * @property {import('aws-cdk-lib/aws-s3').IBucket} bucket - static assets bucket (from StorageStaticStack)
10
+ * @property {string} cdnDomainName - CloudFront domain name (from StorageStaticStack)
11
+ */
12
+
13
+ /**
14
+ * DeploymentTargetStack provisions the EC2 instance (with Elastic IP) that runs the
15
+ * platform services. The S3 bucket lives in StorageStaticStack so that EC2 replacements
16
+ * never trigger CloudFormation cross-stack export conflicts on storage resources.
17
+ *
18
+ * MEDIA_REPOSITORY and MEDIA_CDN_DOMAIN_NAME are derived from the actual bucket and CDN
19
+ * resources rather than being hardcoded in platforms.json.
20
+ */
12
21
  class DeploymentTargetStack extends Stack {
13
22
  constructor(scope, id, props) {
14
23
  super(scope, id, props)
15
24
 
16
25
  if (!props?.config) {
17
- throw ('[DeploymentTargetStack] No template provided')
26
+ throw ('[DeploymentTargetStack] No config provided')
18
27
  }
19
28
 
20
- // TODO: Check if the bucket already exists and use it
21
- // instead of having the whole deployment fail
22
- const staticDeploymentTarget =
23
- new Bucket(this, 'StaticDeploymentTarget', {
24
- bucketName: props.config.awsStaticBucketName,
25
- blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
26
- removalPolicy: RemovalPolicy.RETAIN,
27
- autoDeleteObjects: false,
28
- versioned: true,
29
- cors: [{
30
- allowedHeaders: ['*'],
31
- allowedMethods: ['GET', 'PUT', 'POST' ],
32
- allowedOrigins: [ '*' ],
33
- exposeHeaders: []
34
- }]
35
- })
36
-
37
- const mediaBackupVault = new BackupVault(this, 'StaticMediaBackupVault', {
38
- backupVaultName: `${props.config.platformName}-static-media`,
39
- removalPolicy: RemovalPolicy.RETAIN
40
- })
41
-
42
- const mediaBackupPlan = BackupPlan.daily35DayRetention(this, 'StaticMediaBackupPlan', mediaBackupVault)
43
-
44
- mediaBackupPlan.addSelection('StaticDeploymentTargetBucket', {
45
- backupSelectionName: `${props.config.platformName}-s3-static-media`,
46
- resources: [BackupResource.fromArn(staticDeploymentTarget.bucketArn)],
47
- allowRestores: true
48
- })
49
-
50
- const mediaCDN = new Distribution(this, 'Media', {
51
- defaultBehavior: {
52
- origin: S3BucketOrigin.withOriginAccessControl(staticDeploymentTarget, { originPath: '/media' }),
53
- // S3 bucket CORS does not add headers on viewer responses; without this, browser fetch()
54
- // to the CDN is cross-origin and Chrome may block with net::ERR_BLOCKED_BY_ORB.
55
- responseHeadersPolicy:
56
- ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT
57
- },
58
- priceClass: PriceClass.PRICE_CLASS_100
59
- })
60
-
61
- const containerDeploymentTarget =
62
- new ContainerDeploymentTarget(this, 'ContainerDeploymentTarget', {
63
- config: props.config,
64
- bucket: staticDeploymentTarget
29
+ const containerDeploymentTarget = new ContainerDeploymentTarget(this, 'ContainerDeploymentTarget', {
30
+ config: {
31
+ ...props.config,
32
+ env: {
33
+ ...props.config.env,
34
+ MEDIA_REPOSITORY: props.bucket.bucketName,
35
+ MEDIA_CDN_DOMAIN_NAME: `https://${props.cdnDomainName}`
36
+ }
37
+ },
38
+ bucket: props.bucket
65
39
  })
66
40
 
67
41
  this.containerDeploymentTargetPublicIp = containerDeploymentTarget.instancePublicIp
68
- this.staticDeploymentTargetBucketName = staticDeploymentTarget.bucketName
69
- this.staticDeploymentTargetBucketArn = staticDeploymentTarget.bucketArn
70
-
71
- new CfnOutput(this, 'MediaCDNDomainName', {
72
- value: mediaCDN.domainName,
73
- description: 'Domain name of CDN distribution',
74
- exportName: 'MediaCDNDomainName'
75
- })
76
42
 
77
43
  new CfnOutput(this, 'ContainerDeploymentTargetPublicIp', {
78
44
  value: containerDeploymentTarget.instancePublicIp,
79
- description: 'Public ip of the ec2 instance',
80
- exportName: 'awsContainerDeploymentTargetPublicIp'
81
- })
82
-
83
- new CfnOutput(this, 'StaticDeploymentTargetBucketName', {
84
- value: staticDeploymentTarget.bucketName,
85
- description: 'Name of static deployment target bucket',
86
- exportName: 'awsStaticDeploymentTargetBucketName'
87
- })
88
-
89
- new CfnOutput(this, 'StaticDeploymentTargetBucketArn', {
90
- value: staticDeploymentTarget.bucketArn,
91
- description: 'Name of static deployment target bucket',
92
- exportName: 'awsStaticDeploymentTargetBucketArn'
45
+ description: 'Public IP of the EC2 instance (Elastic IP)',
46
+ exportName: `${props.config.platformName}-ContainerDeploymentTargetPublicIp`
93
47
  })
94
-
95
48
  }
96
49
  }
97
50
 
@@ -0,0 +1,40 @@
1
+ /* eslint-disable no-new */
2
+ const { Stage } = require('aws-cdk-lib')
3
+ const { StorageStaticStack } = require('./storage-static-stack')
4
+ const { DeploymentTargetStack } = require('./deployment-target-stack')
5
+ const { DnsStack } = require('./dns-stack')
6
+ const { SesStack } = require('./ses-stack')
7
+
8
+ /**
9
+ * PlatformStage groups all stacks that make up a single platform deployment.
10
+ * Using a Stage lets you target one platform at a time:
11
+ *
12
+ * cdk deploy 'ossybot/**' --profile ossybot
13
+ *
14
+ * Stack names within the stage are automatically prefixed with the platform name,
15
+ * e.g. ossybot/trust-ci, ossybot/storage-static, ossybot/deployment-target, etc.
16
+ */
17
+ class PlatformStage extends Stage {
18
+ constructor(scope, id, props) {
19
+ super(scope, id, props)
20
+
21
+ const storageStatic = new StorageStaticStack(this, 'storage-static', props)
22
+
23
+ const deploymentTarget = new DeploymentTargetStack(this, 'deployment-target', {
24
+ ...props,
25
+ bucket: storageStatic.bucket,
26
+ cdnDomainName: storageStatic.cdnDomainName
27
+ })
28
+
29
+ new DnsStack(this, 'dns', {
30
+ ...props,
31
+ containerDeploymentTargetPublicIp: deploymentTarget.containerDeploymentTargetPublicIp
32
+ })
33
+
34
+ new SesStack(this, 'ses', props)
35
+ }
36
+ }
37
+
38
+ module.exports = {
39
+ PlatformStage
40
+ }
@@ -0,0 +1,87 @@
1
+ /* eslint-disable no-new */
2
+ const { CfnOutput, Stack, RemovalPolicy } = require('aws-cdk-lib')
3
+ const { BackupPlan, BackupVault, BackupResource } = require('aws-cdk-lib/aws-backup')
4
+ const { Bucket, BlockPublicAccess } = require('aws-cdk-lib/aws-s3')
5
+ const { Distribution, PriceClass, ResponseHeadersPolicy } = require('aws-cdk-lib/aws-cloudfront')
6
+ const { S3BucketOrigin } = require('aws-cdk-lib/aws-cloudfront-origins')
7
+
8
+ /**
9
+ * StorageStaticStack provisions the S3 bucket used for static assets and media,
10
+ * a CloudFront CDN distribution serving the /media prefix, and a daily backup plan.
11
+ *
12
+ * This stack is intentionally kept separate from the deployment-target stack so that
13
+ * the long-lived storage resources are never touched when the EC2 instance is updated
14
+ * or replaced, avoiding CloudFormation cross-stack export conflicts.
15
+ *
16
+ * Exports (consumed by deployment-target-stack via cross-stack reference):
17
+ * - `bucket` — the S3 Bucket construct
18
+ * - `cdnDomainName` — the CloudFront distribution domain name
19
+ */
20
+ class StorageStaticStack extends Stack {
21
+ constructor(scope, id, props) {
22
+ super(scope, id, props)
23
+
24
+ if (!props?.config) {
25
+ throw ('[StorageStaticStack] No config provided')
26
+ }
27
+
28
+ const bucket = new Bucket(this, 'StaticDeploymentTarget', {
29
+ blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
30
+ removalPolicy: RemovalPolicy.RETAIN,
31
+ autoDeleteObjects: false,
32
+ versioned: true,
33
+ cors: [{
34
+ allowedHeaders: ['*'],
35
+ allowedMethods: ['GET', 'PUT', 'POST'],
36
+ allowedOrigins: ['*'],
37
+ exposeHeaders: []
38
+ }]
39
+ })
40
+
41
+ const backupVault = new BackupVault(this, 'StaticMediaBackupVault', {
42
+ removalPolicy: RemovalPolicy.RETAIN
43
+ })
44
+
45
+ const backupPlan = BackupPlan.daily35DayRetention(this, 'StaticMediaBackupPlan', backupVault)
46
+
47
+ backupPlan.addSelection('StaticDeploymentTargetBucket', {
48
+ resources: [BackupResource.fromArn(bucket.bucketArn)],
49
+ allowRestores: true
50
+ })
51
+
52
+ const mediaCDN = new Distribution(this, 'Media', {
53
+ defaultBehavior: {
54
+ origin: S3BucketOrigin.withOriginAccessControl(bucket, { originPath: '/media' }),
55
+ // S3 bucket CORS does not add headers on viewer responses; without this, browser fetch()
56
+ // to the CDN is cross-origin and Chrome may block with net::ERR_BLOCKED_BY_ORB.
57
+ responseHeadersPolicy: ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT
58
+ },
59
+ priceClass: PriceClass.PRICE_CLASS_100
60
+ })
61
+
62
+ this.bucket = bucket
63
+ this.cdnDomainName = mediaCDN.domainName
64
+
65
+ new CfnOutput(this, 'MediaCDNDomainName', {
66
+ value: mediaCDN.domainName,
67
+ description: 'Domain name of CDN distribution',
68
+ exportName: `${props.config.platformName}-MediaCDNDomainName`
69
+ })
70
+
71
+ new CfnOutput(this, 'StaticDeploymentTargetBucketName', {
72
+ value: bucket.bucketName,
73
+ description: 'Name of static deployment target bucket',
74
+ exportName: `${props.config.platformName}-StaticDeploymentTargetBucketName`
75
+ })
76
+
77
+ new CfnOutput(this, 'StaticDeploymentTargetBucketArn', {
78
+ value: bucket.bucketArn,
79
+ description: 'ARN of static deployment target bucket',
80
+ exportName: `${props.config.platformName}-StaticDeploymentTargetBucketArn`
81
+ })
82
+ }
83
+ }
84
+
85
+ module.exports = {
86
+ StorageStaticStack
87
+ }
@@ -10,10 +10,7 @@ const { logError, logInfo } = require('../log')
10
10
  * @property {string} awsAccountId - Aws account id
11
11
  * @property {string=} awsRegion - ?
12
12
  * @property {string=} awsKeyPairName - ?
13
- * @property {string} awsRoleToAssume - ?
14
13
  * @property {string=} awsDeploymentSqsArn - ?
15
- *
16
- * @property {string} ciGithubActionsRepo - organisation/repoName
17
14
  */
18
15
 
19
16
  /**
@@ -1,87 +0,0 @@
1
- const core = require('@actions/core')
2
- const { STSClient, AssumeRoleWithWebIdentityCommand } = require('@aws-sdk/client-sts')
3
-
4
- const { logInfo, logError } = require('../log')
5
-
6
- /**
7
- * @class
8
- */
9
- class AwsCredentialsService {
10
-
11
- static resolveAwsCredentials(platformConfig) {
12
- // If awsRoleToAssume is present, then we assume we run in a github workflow
13
- // If awsRoleToAssume is not present, then we assume they are resolved localy by aws-sdk
14
- if (!platformConfig.awsRoleToAssume) {
15
- logInfo({ message: '[AwsCredentialsService] No aws role to assume was found, leaving auth logic to @aws-sdk package' })
16
- return Promise.resolve(undefined)
17
- }
18
-
19
- const stsClient = new STSClient({ region: platformConfig.awsRegion })
20
-
21
- logInfo({ message: '[AwsCredentialsService] Fetching GitHub ID token' })
22
- return core.getIDToken('sts.amazonaws.com')
23
- .then(webIdentityToken => {
24
- logInfo({ message: `[AwsCredentialsService] Attempting to resolve aws credentials by assuming the role: ${platformConfig.awsRoleToAssume}` })
25
- return stsClient.send(new AssumeRoleWithWebIdentityCommand({
26
- RoleArn: `arn:aws:iam::${platformConfig.awsAccountId}:role/${platformConfig.awsRoleToAssume}`,
27
- RoleSessionName: 'GitHubActions',
28
- DurationSeconds: 15 * 60,
29
- WebIdentityToken: webIdentityToken
30
- }))
31
- })
32
- .then(responseData => ({
33
- // Don't ask
34
- AccessKeyId: responseData.Credentials.AccessKeyId,
35
- SessionToken: responseData.Credentials.SessionToken,
36
- SecretAccessKey: responseData.Credentials.SecretAccessKey,
37
- accessKeyId: responseData.Credentials.AccessKeyId,
38
- sessionToken: responseData.Credentials.SessionToken,
39
- secretAccessKey: responseData.Credentials.SecretAccessKey
40
- }))
41
- .then(x => AwsCredentialsService.exportCredentialsToGithubWorkflow({ ...x, awsRegion: platformConfig.awsRegion }))
42
- .catch(error => {
43
- logError({ message: '[AwsCredentialsService] Could not resolve temporary credentials', error })
44
- return undefined
45
- })
46
- }
47
-
48
- static exportCredentialsToGithubWorkflow(params) {
49
- // Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets.
50
- // Setting the credentials as secrets masks them in Github Actions logs
51
- const { accessKeyId, secretAccessKey, sessionToken, awsRegion } = params
52
-
53
- // AWS_ACCESS_KEY_ID:
54
- // Specifies an AWS access key associated with an IAM user or role
55
- core.setSecret(accessKeyId)
56
- core.exportVariable('AWS_ACCESS_KEY_ID', accessKeyId)
57
-
58
- // AWS_SECRET_ACCESS_KEY:
59
- // Specifies the secret key associated with the access key. This is essentially the "password" for the access key.
60
- core.setSecret(secretAccessKey)
61
- core.exportVariable('AWS_SECRET_ACCESS_KEY', secretAccessKey)
62
-
63
- // AWS_SESSION_TOKEN:
64
- // Specifies the session token value that is required if you are using temporary security credentials.
65
- if (sessionToken) {
66
- core.setSecret(sessionToken)
67
- core.exportVariable('AWS_SESSION_TOKEN', sessionToken)
68
- } else if (process.env.AWS_SESSION_TOKEN) {
69
- // clear session token from previous credentials action
70
- core.exportVariable('AWS_SESSION_TOKEN', '')
71
- }
72
-
73
- if (awsRegion) {
74
- core.exportVariable('AWS_REGION', awsRegion)
75
- } else if (process.env.AWS_REGION) {
76
- // clear AWS_REGION from previous credentials action
77
- core.exportVariable('AWS_REGION', '')
78
- }
79
-
80
- return params
81
- }
82
-
83
- }
84
-
85
- module.exports = {
86
- AwsCredentialsService
87
- }
@@ -1,55 +0,0 @@
1
- const arg = require('arg')
2
- const { AwsCredentialsService } = require('./aws-credentials')
3
- const { PlatformTemplateService } = require('../template')
4
- const { PlatformConfigService } = require('../config')
5
- const { logInfo, logError } = require('../log')
6
-
7
- const resolveCredentials = options => {
8
- logInfo({ message: 'resolve-credentials' })
9
-
10
- const parsedArgs = arg({
11
- '--access-key-id': String,
12
- '--session-token': String,
13
- '--secret-access-key': String
14
- }, { argv: options })
15
-
16
- AwsCredentialsService.exportCredentialsToGithubWorkflow({
17
- accessKeyId: parsedArgs['--access-key-id'],
18
- sessionToken: parsedArgs['--session-token'],
19
- secretAccessKey: parsedArgs['--secret-access-key']
20
- })
21
- }
22
-
23
- const assumeRole = options => {
24
- logInfo({ message: 'assume-role' })
25
-
26
- const parsedArgs = arg({
27
- '--platforms': String,
28
- '--target-platform': String
29
- }, { argv: options })
30
-
31
- const platformName = parsedArgs['--target-platform'] || ''
32
-
33
- PlatformTemplateService.readFromFile(parsedArgs['--platforms'] || process.env.PLATFORMS)
34
- .then(templates => templates.map(PlatformConfigService.from))
35
- .then(configs => configs.find(x => x.platformName === platformName))
36
- .then(targetConfig => {
37
-
38
- if (!targetConfig) {
39
- return logError({ message: 'No configuration found' })
40
- }
41
-
42
- AwsCredentialsService.resolveAwsCredentials(targetConfig)
43
- .then(() => logInfo({ message: `Assumed role for ${targetConfig.platformName}` }))
44
-
45
- })
46
-
47
- }
48
-
49
- module.exports = {
50
- handler: ([command, ...options]) => {
51
- !!command
52
- ? { 'resolve-credentials': resolveCredentials, 'assume-role': assumeRole }[command](options)
53
- : logError({ message: 'No command provided' })
54
- }
55
- }
@@ -1 +0,0 @@
1
- module.exports = require('./aws-credentials')
package/src/index.cli.js DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env node
2
- /* eslint-disable global-require, no-unused-vars */
3
-
4
- const [_, __, handlerName, ...restArgs] = process.argv
5
-
6
- const loadHandler = {
7
- aws: () => require('./aws-credentials/cli.js'),
8
- template: () => require('./template/cli.js'),
9
- }[handlerName]
10
-
11
- !!loadHandler && loadHandler().handler(restArgs)
@@ -1,52 +0,0 @@
1
- // TODO: remove this service once ossy.se traffic flows through the platform runtime.
2
- // The platform runtime (ghcr.io/ossy-se/platform) is the long-term host for all
3
- // tenant sites including ossy.se. This service is a stopgap while the CMS-based
4
- // publish flow is validated in production.
5
-
6
- const systemdServiceFile = `
7
- [Unit]
8
- Description=Ossy Website (ossy.se)
9
- After=network.target docker.service
10
- Requires=docker.service
11
-
12
- [Service]
13
- EnvironmentFile=/etc/environment
14
- ExecStartPre=-/usr/bin/docker rm -f ossy-website-ossy
15
- ExecStartPre=/usr/bin/docker pull ghcr.io/ossy-se/website-ossy:latest
16
- ExecStart=/usr/bin/docker run --name ossy-website-ossy \\
17
- --network ossy-network \\
18
- -p 3002:3000 \\
19
- -e PORT=3000 \\
20
- -e OSSY_API_URL=http://ossy-api:3001 \\
21
- -e OSSY_COOKIE_SECRET=\${OSSY_COOKIE_SECRET} \\
22
- ghcr.io/ossy-se/website-ossy:latest
23
- ExecStop=/usr/bin/docker stop ossy-website-ossy
24
- Restart=on-failure
25
- RestartSec=5
26
-
27
- [Install]
28
- WantedBy=multi-user.target
29
- `
30
-
31
- /**
32
- * @class
33
- */
34
- class OssyWebsiteOssyService {
35
-
36
- static install() {
37
- return [`sudo tee /etc/systemd/system/ossy-website-ossy.service > /dev/null << 'EOF'\n${systemdServiceFile}\nEOF`]
38
- }
39
-
40
- static enable() {
41
- return ['sudo systemctl enable ossy-website-ossy']
42
- }
43
-
44
- static start() {
45
- return ['sudo systemctl start ossy-website-ossy']
46
- }
47
-
48
- }
49
-
50
- module.exports = {
51
- OssyWebsiteOssyService
52
- }