@ossy/deployment-tools 0.0.67 → 0.0.69

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.
Files changed (28) hide show
  1. package/README.md +61 -1
  2. package/package.json +2 -1
  3. package/src/aws-credentials/cli.js +1 -2
  4. package/src/caddy/caddy-config.js +24 -26
  5. package/src/caddy/caddy.js +5 -43
  6. package/src/config/platform-config.js +29 -17
  7. package/src/deploy/cli.js +15 -9
  8. package/src/deploy/index.js +1 -0
  9. package/src/deploy/platform-deployment.js +32 -66
  10. package/src/infrastructure/cli.js +29 -18
  11. package/src/infrastructure/{container-server/container-server.js → container-deployment-target/container-deployment-target.js} +29 -43
  12. package/src/infrastructure/container-deployment-target/index.js +3 -0
  13. package/src/infrastructure/deployment-target-stack.js +46 -0
  14. package/src/infrastructure/dns-stack.js +61 -0
  15. package/src/infrastructure/{establish-trust-stack.js → trust-ci-stack.js} +3 -3
  16. package/src/server/platform-server.js +1 -3
  17. package/src/template/deployment-template.js +86 -0
  18. package/src/template/index.js +4 -1
  19. package/src/template/platform-template.js +1 -6
  20. package/src/caddy/caddy.playground.js +0 -21
  21. package/src/infrastructure/container-server/index.js +0 -3
  22. package/src/infrastructure/platform-stack.js +0 -58
  23. package/src/server/platform-server.playground.js +0 -10
  24. package/src/server/rest-api.js +0 -31
  25. /package/src/infrastructure/{container-server → container-deployment-target}/aws-profile.js +0 -0
  26. /package/src/infrastructure/{container-server → container-deployment-target}/caddy.service.js +0 -0
  27. /package/src/infrastructure/{container-server → container-deployment-target}/deployment-tools.service.js +0 -0
  28. /package/src/infrastructure/{container-server → container-deployment-target}/user-data-commands.js +0 -0
package/README.md CHANGED
@@ -37,11 +37,71 @@ npx --yes @ossy/deployment-tools deploy \
37
37
  --ossyfile packages/${{ github.event.inputs.packageName }}/ossy.json \
38
38
  ```
39
39
 
40
- ## Infrastructure
40
+ ## Concepts
41
+
42
+ **Workspace**
43
+
44
+ A workspace is an umbrella for our services.
45
+ It needs to be associated with at least one billable account.
46
+ The workspace holds information like what tools and services are in use and should
47
+ be billed, and what users have access to these tools and services.
48
+
49
+ ```
50
+ id: string;
51
+ name: string;
52
+ participants: WorkspaceParticipant[];
53
+ services: ServiceDefinition[];
54
+ billingInformation
55
+
56
+ ```
57
+
58
+ ## Our domains
59
+
60
+ ossy.se
61
+ www.ossy.se
62
+ test.ossy.se
63
+
64
+ api.ossy.se
65
+ api.test.ossy.se
66
+
67
+ plexus.ossy.se
68
+ plexus.test.ossy.se
69
+
70
+ oskarssylwan.se
71
+ www.oskarssylwan.se
72
+ test.oskarssylwan.se
73
+
74
+
75
+ ## Overview of our infrastructure
41
76
 
42
77
  We use AWS to host our infrastructure and all of it is defined in JavaScript with the help of
43
78
  (AWS CDK)[https://aws.amazon.com/cdk/].
44
79
 
80
+ **Static content**
81
+
82
+ We use a s3 bucket for static content.
83
+ This bucket is used to host websites, images, videos and other static content.
84
+ On root level you'll find directories that represent one workspace each.
85
+
86
+ /<workspacId>/<service>/
87
+ /<workspacId>/websites/website-id
88
+
89
+ The bucket have directories for each workspace that is the workspaceID.
90
+
91
+ The bucket have directories for each workspace that contains a media directory and website directories
92
+
93
+ - a place to host media files like images, videos, pdf documents etc.
94
+ - a platform to host different docker images to
95
+ - a mongodb database that can ensure data persistance without much effort from our our side
96
+ - an email service
97
+ - an easy way to host multiple single page applications
98
+
99
+ Stacks
100
+
101
+ - email service stack per env
102
+ - media bucket stack per env
103
+ - stack for dns records with the account that holds domain names
104
+
45
105
  ### Adding a new AWS account
46
106
  We have a different account for each service and environment.
47
107
  To add a new account follow the steps below.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/deployment-tools",
3
- "version": "0.0.67",
3
+ "version": "0.0.69",
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",
@@ -21,6 +21,7 @@
21
21
  "aws-cdk-lib": "^2.59.0",
22
22
  "constructs": "^10.1.211",
23
23
  "express": "^4.18.1",
24
+ "glob": "^9.3.2",
24
25
  "nanoid": "^3.3.4",
25
26
  "node-fetch": "^2.6.7"
26
27
  },
@@ -29,8 +29,7 @@ const assumeRole = options => {
29
29
  '--target-platform': String
30
30
  }, { argv: options })
31
31
 
32
- const stackname = parsedArgs['--target-platform'] || ''
33
- const [platformName] = stackname.split('-')
32
+ const platformName = parsedArgs['--target-platform'] || ''
34
33
 
35
34
  PlatformTemplateService.readFromFile(parsedArgs['--platforms'] || process.env.PLATFORMS)
36
35
  .then(templates => templates.map(PlatformConfigService.from))
@@ -1,3 +1,6 @@
1
+ const { DeploymentTemplateService } = require('../template')
2
+ const { SupportedEnvironments } = require('../config')
3
+
1
4
  const Matchers = {
2
5
  host: host => ({ host: [host] }),
3
6
  path: path => ({ path: [path] })
@@ -19,48 +22,43 @@ const Handlers = {
19
22
  */
20
23
  class CaddyConfigService {
21
24
 
22
- static getRouteConfig(platformConfig, deploymentRequest) {
23
-
24
- const url = [
25
- deploymentRequest.subdomain,
26
- platformConfig.activeEnvironment !== 'prod' ? platformConfig.activeEnvironment : undefined,
27
- platformConfig.domain
28
- ]
29
- .filter(x => !!x)
30
- .join('.')
25
+ static createConfig(platformConfig, deploymentTemplates) {
31
26
 
32
- return {
33
- match: [Matchers.host(url)],
34
- handle: [Handlers.subroute([{ handle: [Handlers.reverseProxy(deploymentRequest.hostPort)]}])]
35
- }
36
- }
27
+ const containerDeployments = DeploymentTemplateService
28
+ .getContainerDeploymentsForEnvironmentType(platformConfig.environmentType, deploymentTemplates)
37
29
 
38
- static getDefaultConfig(platformConfig) {
39
30
  return {
40
31
  apps: {
41
32
  http: {
42
33
  servers: {
43
- [platformConfig.ciServerName]: {
34
+ 'deployment-tools': {
44
35
  listen: [':80', ':443'],
45
- routes: [
46
- {
47
- match: [Matchers.host(`${platformConfig.ciSubDomain}.${platformConfig.activeEnvironment}.${platformConfig.domain}`)],
48
- handle: [Handlers.subroute([{ handle: [Handlers.reverseProxy(platformConfig.ciInternalServerPort)]}])]
49
- }
50
- ]
36
+ routes: containerDeployments.map(deploymentTemplate => ({
37
+ match: [Matchers.host(deploymentRequest.domain)],
38
+ handle: [Handlers.subroute([{ handle: [Handlers.reverseProxy(deploymentRequest.hostPort)]}])]
39
+ }))
51
40
  }
52
41
  }
53
42
  },
54
- tls: platformConfig.activeEnvironment === 'local-dev' ? undefined : {
43
+ tls: platformConfig.environmentType === 'local-dev' ? undefined : {
55
44
  automation: {
56
45
  policies: [
57
46
  {
58
- subjects:[`*.${platformConfig.activeEnvironment}.${platformConfig.domain}`],
47
+ subjects: DeploymentTemplateService.groupDeploymentDomainsByRootDomain(containerDeployments).keys().flatMap(rootDomain => {
48
+ const environmentSubdomain = platformConfig.environmentType === SupportedEnvironments.PROD
49
+ ? ''
50
+ : `${platformConfig.environmentType}.`
51
+
52
+ return [
53
+ `*.${environmentSubdomain}${rootDomain}`,
54
+ `${environmentSubdomain}${rootDomain}`
55
+ ]
56
+ }),
59
57
  issuers:[
60
58
  {
61
- challenges:{
59
+ challenges: {
62
60
  dns:{
63
- provider:{
61
+ provider: {
64
62
  'max_retries': 10,
65
63
  name: 'route53',
66
64
  'aws_profile': 'ci-client'
@@ -7,55 +7,17 @@ const { logInfo, logError, logDebug } = require('../log')
7
7
  */
8
8
  class CaddyService {
9
9
 
10
- static addDeployment(platformConfig, deploymentRequest) {
11
- const routeConfig = CaddyConfigService.getRouteConfig(platformConfig, deploymentRequest)
12
- const host = routeConfig.match[0].host[0]
13
- logInfo({ message: `[CaddyService] Updating caddy config to route ${host} to localhost:${deploymentRequest.hostPort}` })
14
-
15
- return fetch(`http://localhost:2019/config/apps/http/servers/${platformConfig.ciServerName}/routes`, {
10
+ static applyConfig(config) {
11
+ return fetch('http://localhost:2019/load', {
16
12
  method: 'POST',
17
13
  headers: { 'Content-Type': 'application/json' },
18
- body: JSON.stringify(routeConfig)
14
+ body: JSON.stringify(config)
19
15
  })
20
- .then(response => response?.error && logError({ message: `[CaddyService] Could not update caddy config to include ${host}`, error }))
21
- .catch(error => logError({ message: `[CaddyService] Could not update caddy config to include ${host}`, error }))
16
+ .then(response => response?.error && logError({ message: '[CaddyService] Could not apply default caddy config', error }))
17
+ .catch(error => logError({ message: '[CaddyService] Could not apply default caddy config', error }))
22
18
  }
23
19
 
24
- static applyDefaultConfig(platformConfig) {
25
- return CaddyService.fetchServerConfig(platformConfig)
26
- .then(caddyConfig => {
27
-
28
- if (caddyConfig) {
29
- logInfo({ message: '[CaddyService] Server config already exist, not applying default config' })
30
- return
31
- }
32
20
 
33
- logInfo({ message: '[CaddyService] Applying default caddy config' })
34
- const defaultConfig = CaddyConfigService.getDefaultConfig(platformConfig)
35
- return fetch('http://localhost:2019/load', {
36
- method: 'POST',
37
- headers: { 'Content-Type': 'application/json' },
38
- body: JSON.stringify(defaultConfig)
39
- })
40
- })
41
- .then(response => response?.error && logError({ message: '[CaddyService] Could not apply default caddy config', error }))
42
- .catch(error => logError({ message: '[CaddyService] Could not apply default caddy config', error }))
43
-
44
- }
45
-
46
- static fetchServerConfig(platformConfig) {
47
- logInfo({ message: '[CaddyService] Fetching server config' })
48
- return fetch(`http://localhost:2019/config/apps/http/servers/${platformConfig.ciServerName}`, {
49
- method: 'GET',
50
- headers: { 'Content-Type': 'application/json' }
51
- })
52
- .then(x => x.json())
53
- .then(response => {
54
- if (!response?.error) return response
55
- logInfo({ message: '[CaddyService] No server config found' })
56
- logDebug({ message: '[CaddyService] Error while fetching server config', data: response.error })
57
- })
58
- }
59
21
  }
60
22
 
61
23
  module.exports = {
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Platform config definition
3
+ * @typedef {Object} PlatformConfig
4
+ * @property {string} platformName - Name of platform
5
+ * @property {string} environmentType - local, test, qa, prod
6
+ *
7
+ * @property {string} awsAccountId - Aws account id
8
+ * @property {string=} awsRegion - ?
9
+ * @property {string=} awsKeyPairName - ?
10
+ * @property {string} awsRoleToAssume - ?
11
+ * @property {string=} awsDeploymentSqsName - ?
12
+ * @property {string=} awsDeploymentSqsArn - ?
13
+ *
14
+ * @property {string=} ciDockerNetworkName - ?
15
+ * @property {string} ciGithubActionsRepo - organisation/repoName
16
+ */
17
+
1
18
  const SupportedRegions = {
2
19
  North: 'eu-north-1'
3
20
  }
@@ -11,16 +28,10 @@ const SupportedEnvironments = {
11
28
  }
12
29
 
13
30
  const SupportedDeploymentTypes = {
14
- Container: 'CONTAINER'
15
- // Static = 'STATIC'
31
+ Container: 'CONTAINER',
32
+ Static: 'STATIC'
16
33
  }
17
34
 
18
- // export interface PlatformConfig extends Required<Omit<PlatformTemplate, 'awsRoleToAssume' | 'awsKeyPairName'>> {
19
- // activeEnvironment: SupportedEnvironments;
20
- // awsRoleToAssume?: string;
21
- // awsKeyPairName?: string;
22
- // }
23
-
24
35
  /**
25
36
  * @class
26
37
  */
@@ -31,17 +42,17 @@ class PlatformConfigService {
31
42
  const withDefaults = {
32
43
  platformName: SupportedEnvironments.LOCAL,
33
44
  domain: 'localhost',
34
- activeEnvironment: SupportedEnvironments.LOCAL,
35
- supportedDeploymentTypes: ['CONTAINER'],
36
- ciSubDomain: 'ci',
37
- ciInternalServerPort: 3000,
38
- ciServerName: 'ci-client',
39
- ciDockerNetworkName: 'deployment-tools',
45
+ environmentType: SupportedEnvironments.LOCAL,
40
46
  awsRegion: SupportedRegions.North,
41
- ...template
47
+ ...template,
48
+ // values below should not be overriden by the template properties
49
+ ciDockerNetworkName: 'deployment-tools',
42
50
  }
43
51
 
44
- const awsDeploymentSqsArn = `https://sqs.${withDefaults.awsRegion}.amazonaws.com/${withDefaults.awsAccountId}/${withDefaults.platformName}-${withDefaults.activeEnvironment}`
52
+ const awsDeploymentSqsName = `${withDefaults.platformName}-container-deployments-requests`
53
+
54
+ const awsDeploymentSqsArn =
55
+ `https://sqs.${withDefaults.awsRegion}.amazonaws.com/${withDefaults.awsAccountId}/${awsDeploymentSqsName}`
45
56
 
46
57
  const awsRoleToAssume = process.env.CI
47
58
  ? `github-ci-role-${withDefaults.platformName}`
@@ -49,8 +60,9 @@ class PlatformConfigService {
49
60
 
50
61
  return {
51
62
  ...withDefaults,
63
+ awsDeploymentSqsName,
52
64
  awsDeploymentSqsArn,
53
- awsRoleToAssume
65
+ awsRoleToAssume,
54
66
  }
55
67
 
56
68
  }
package/src/deploy/cli.js CHANGED
@@ -13,22 +13,28 @@ const deploy = options => {
13
13
  '--authentication': String,
14
14
  '--a': '--authentication',
15
15
 
16
- '--target-env': String,
17
- '-t': '--target-env',
16
+ '--domain': String,
17
+ '-d': '--domain',
18
+
19
+ '--platform': String,
20
+ '-p': '--platform',
21
+
22
+ '--platforms-path': String,
23
+ '-pp': '--platforms-path',
24
+
25
+ '--ossy-files': String,
26
+ '-o': '--ossy-files',
18
27
 
19
- '--ossyfile': String,
20
- '-o': '--ossyfile',
21
28
 
22
- '--platforms': String,
23
- '-p': '--platforms'
24
29
  }, { argv: options })
25
30
 
26
31
  PlatformDeploymentService.deploy({
27
32
  username: parsedArgs['--username'],
28
33
  authentication: parsedArgs['--authentication'],
29
- targetEnvironment: parsedArgs['--target-env'],
30
- pathToPlatformTemplates: parsedArgs['--platforms'],
31
- pathToOssyFile: parsedArgs['--ossyfile']
34
+ targetDomain: parsedArgs['--domain'],
35
+ targetPlatform: parsedArgs['--platform'],
36
+ pathToPlatformTemplates: parsedArgs['--platforms-path'],
37
+ pathToOssyFile: parsedArgs['--ossy-files']
32
38
  })
33
39
  }
34
40
 
@@ -0,0 +1 @@
1
+ module.exports = require('./platform-deployment.js')
@@ -1,37 +1,11 @@
1
- const { resolve } = require('path')
2
1
  const { readFileSync } = require('fs')
3
- const { PlatformTemplateService } = require('../template')
4
- const { PlatformConfigService, SupportedDeploymentTypes } = require('../config')
2
+ const { resolve } = require('path')
3
+ const { PlatformTemplateService, DeploymentTemplateService } = require('../template')
4
+ const { PlatformConfigService, SupportedDeploymentTypes, SupportedEnvironments } = require('../config')
5
5
  const { DeploymentQueueService } = require('../deployment-queue')
6
+ const { CaddyConfigService } = require('../caddy')
6
7
  const { logError } = require('../log')
7
8
 
8
- // export interface DeploymentTemplate {
9
- // type: SupportedDeploymentTypes;
10
- // targetDeploymentPlatform: string;
11
- // subdomain?: string;
12
- // env?: {
13
- // shared?: { [name: string]: string | number };
14
- // prod?: { [name: string]: string | number };
15
- // test?: { [name: string]: string | number };
16
- // qa?: { [name: string]: string | number };
17
- // }
18
- // }
19
-
20
- // export interface ContainerDeploymentTemplate extends DeploymentTemplate {
21
- // type: SupportedDeploymentTypes.Container;
22
- // dockerFile: string;
23
- // dockerContext: string;
24
- // image: string;
25
- // hostPort: number;
26
- // containerPort: number;
27
- // registry: string;
28
- // }
29
-
30
- // export interface ContainerDeploymentRequest extends ContainerDeploymentTemplate {
31
- // authentication?: string;
32
- // username?: string;
33
- // }
34
-
35
9
  /**
36
10
  * @class
37
11
  */
@@ -40,63 +14,55 @@ class PlatformDeploymentService {
40
14
  static deploy({
41
15
  username,
42
16
  authentication,
43
- targetEnvironment,
17
+ targetDomain,
18
+ targetPlatform,
44
19
  pathToPlatformTemplates,
45
- pathToOssyFile
20
+ globPatternForOssyFiles
46
21
  }) {
47
22
 
48
- const platformConfigRequest = PlatformTemplateService.readFromFile(pathToPlatformTemplates)
49
- .then(templates => templates.map(x => ({ ...x, activeEnvironment: targetEnvironment })))
50
- .then(templates => templates.map(x => PlatformConfigService.from(x)))
23
+ return Promise.all([
24
+ DeploymentTemplateService.readOssyFiles(globPatternForOssyFiles),
25
+ PlatformTemplateService.readFromFile(pathToPlatformTemplates)
26
+ .then(templates => templates.map(PlatformConfigService.from))
27
+ ])
28
+ .then(([deploymentTemplates, platformConfigs]) => {
51
29
 
52
- const deploymentTemplatesRequest = PlatformDeploymentService.getDeploymentTemplates(pathToOssyFile)
30
+ const platformConfig = platformConfigs.find(({ platformName }) => platformName === targetPlatform)
31
+ const deploymentTemplatesForTargetPlatform = deploymentTemplates[targetPlatform] || []
32
+ const deploymentTemplate = deploymentTemplatesForTargetPlatform.find(({ domain }) => domain === targetDomain)
53
33
 
54
- return Promise.all([platformConfigRequest, deploymentTemplatesRequest])
55
- .then(([platformConfigs, deploymentTemplates]) => {
56
- deploymentTemplates.map(deploymentTemplate => {
34
+ if (!platformConfig) {
35
+ logError({ message: `[PlatformDeploymentService] Could not find a platform named ${targetPlatform}` })
36
+ return Promise.reject()
37
+ }
57
38
 
58
- const platformConfig = platformConfigs.find(config => config.platformName === deploymentTemplate.targetDeploymentPlatform)
39
+ if (!deploymentTemplate) {
40
+ logError({ message: `[PlatformDeploymentService] Could not find a deployment template for ${targetDomain} in ${targetPlatform}` })
41
+ return Promise.reject()
42
+ }
59
43
 
60
- if (!platformConfig) {
61
- logError({ message: `[PlatformDeploymentService] Could not find a deployment platform with the name ${deploymentTemplate.targetDeploymentPlatform}` })
62
- return Promise.reject()
63
- }
44
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
64
45
 
65
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
46
+ if (deploymentTemplate.type === SupportedDeploymentTypes.Container) {
66
47
 
67
- if (deploymentTemplate.type !== SupportedDeploymentTypes.Container) {
68
- logError({ message: `[PlatformDeploymentService] Unsupported deployment type of ${deploymentTemplate.type}` })
69
- return Promise.reject()
70
- }
48
+ const caddyConfig = CaddyConfigService.createConfig(platformConfig, deploymentTemplatesForTargetPlatform)
71
49
 
72
50
  const deploymentRequest = {
73
51
  ...deploymentTemplate,
74
- env: PlatformDeploymentService.getEnvironmentVariables(targetEnvironment, deploymentTemplate),
75
52
  username: username,
76
- authentication: authentication
53
+ authentication: authentication,
54
+ caddyConfig
77
55
  }
78
56
 
79
57
  return DeploymentQueueService.sendDeploymentRequest(platformConfig, deploymentRequest)
58
+ }
80
59
 
81
- })
60
+ logError({ message: `[PlatformDeploymentService] Unsupported deployment type of ${deploymentTemplate.type}` })
61
+ return Promise.reject()
82
62
  })
83
63
  .catch(error => logError({ message: '[PlatformDeploymentService] Could not send deployment request', error }))
84
64
  }
85
65
 
86
- static getDeploymentTemplates(pathToOssyFile) {
87
- if (!pathToOssyFile) return logError({ message: '[PlatformDeploymentService] No path to ossy.json provided' })
88
- const ossyfile = JSON.parse(readFileSync(resolve(pathToOssyFile), 'utf8'))
89
- return Promise.resolve(ossyfile.deployments || [])
90
- }
91
-
92
- static getEnvironmentVariables(targetEnvironment, deploymentRequest) {
93
- const envs = deploymentRequest.env || {}
94
- return {
95
- ...(envs.shared || {}),
96
- ...(envs[targetEnvironment] || {})
97
- }
98
- }
99
-
100
66
  }
101
67
 
102
68
  module.exports = {
@@ -1,30 +1,41 @@
1
1
  #!/usr/bin/env node
2
2
  /* eslint-disable no-new */
3
3
  const { App } = require('aws-cdk-lib')
4
-
5
- const { PlatformStack } = require('./platform-stack')
6
- const { EstablishTrustStack } = require('./establish-trust-stack')
7
-
8
- const { PlatformTemplateService } = require('../template')
4
+ const { TrustCiStack } = require('./trust-ci-stack')
5
+ const { DeploymentTargetStack } = require('./deployment-target-stack')
6
+ const { DnsStack } = require('./dns-stack')
7
+ const { PlatformTemplateService, DeploymentTemplateService } = require('../template')
9
8
  const { PlatformConfigService } = require('../config')
10
-
11
- PlatformTemplateService
12
- .readFromFile(process.env.PLATFORMS)
13
- .then(templates => templates.map(PlatformConfigService.from))
14
- .then(configs => {
15
-
9
+ const { PlatformDeploymentService } = require('../deploy')
10
+
11
+ Promise.all([
12
+ DeploymentTemplateService.readOssyFiles('../../../**/ossy.json'),
13
+ PlatformTemplateService
14
+ .readFromFile(process.env.PLATFORMS)
15
+ .then(templates => templates.map(PlatformConfigService.from))
16
+ ])
17
+ .then(([deploymentMap, configs]) => {
16
18
  const app = new App()
17
19
 
18
20
  configs.forEach(config => {
19
- const env = { account: config.awsAccountId, region: config.awsRegion }
20
21
 
21
- new EstablishTrustStack(app, `${config.platformName}-establish-trust`, { config, env })
22
+ const stackProps = {
23
+ config,
24
+ env: {
25
+ account: config.awsAccountId,
26
+ region: config.awsRegion
27
+ }
28
+ }
29
+
30
+ new TrustCiStack(app, `${config.platformName}-trust-ci`, stackProps)
31
+
32
+ const deploymentTarget = new DeploymentTargetStack(app, `${config.platformName}-deployment-target`, stackProps)
22
33
 
23
- config.supportedEnvironments
24
- .map(activeEnvironment => PlatformConfigService.from({ ...config, activeEnvironment }))
25
- .forEach(config => {
26
- new PlatformStack(app, `${config.platformName}-${config.activeEnvironment}`, { config, env })
27
- })
34
+ new DnsStack(app, `${config.platformName}-dns`, {
35
+ ...stackProps,
36
+ deployments: deploymentMap[config.platformName],
37
+ containerDeploymentTargetPublicIp: deploymentTarget.containerDeploymentTargetPublicIp
38
+ })
28
39
 
29
40
  })
30
41
  })
@@ -1,4 +1,4 @@
1
- const { CfnOutput } = require('aws-cdk-lib')
1
+ const { CfnOutput, Duration, RemovalPolicy } = require('aws-cdk-lib')
2
2
  const { Construct } = require('constructs')
3
3
  const {
4
4
  Instance,
@@ -12,25 +12,23 @@ const {
12
12
  Port,
13
13
  UserData
14
14
  } = require('aws-cdk-lib/aws-ec2')
15
- const { HostedZone, ARecord, RecordTarget } = require('aws-cdk-lib/aws-route53')
16
15
  const { Role, ServicePrincipal, Policy, PolicyStatement, Effect } = require('aws-cdk-lib/aws-iam')
17
- const {
18
- getInstallNodeJs,
19
- getInstallNpm,
20
- getInstallDocker
21
- } = require('./user-data-commands')
16
+ const { Queue } = require('aws-cdk-lib/aws-sqs')
17
+ const { Source, BucketDeployment } = require('aws-cdk-lib/aws-s3-deployment')
18
+ const { getInstallNodeJs, getInstallNpm, getInstallDocker } = require('./user-data-commands')
22
19
  const { CaddyService } = require('./caddy.service')
23
20
  const { DeploymentToolsService } = require('./deployment-tools.service')
24
21
  const { AwsProfile } = require('./aws-profile')
25
22
  const { SupportedRegions } = require('../../config')
26
23
 
27
24
  /**
28
- * Platform template definition
25
+ * ContainerServerProps
29
26
  * @namespace ContainerServer
30
27
  * @typedef {Object} ContainerServerProps
31
- * @property {PlatformConfig} platformConfig - platform config
32
- * @property {Bucket} platformConfigBucket - aws bucket
28
+ * @property {PlatformConfig} config - platform config
29
+ * @property {Bucket} bucket - s3 bucket
33
30
  */
31
+
34
32
  const InstanceImages = {
35
33
  UBUNTU: 'ami-092cce4a19b438926'
36
34
  }
@@ -38,11 +36,9 @@ const InstanceImages = {
38
36
  /**
39
37
  * @class
40
38
  */
41
- class ContainerServer extends Construct {
39
+ class ContainerDeploymentTarget extends Construct {
42
40
 
43
41
  /**
44
- * EC2 Instance that docker containers are served on
45
- *
46
42
  * @param {object} scope - scope
47
43
  * @param {string} id - id
48
44
  * @param {ContainerServerProps} props - ContainerServerProps
@@ -50,18 +46,8 @@ class ContainerServer extends Construct {
50
46
  constructor(scope, id, props) {
51
47
  super(scope, id)
52
48
 
53
- const hostedZone = new HostedZone(this, 'HostedZone', {
54
- zoneName: `${props.platformConfig.activeEnvironment}.${props.platformConfig.domain}`
55
- })
56
-
57
- const vpc = Vpc.fromLookup(this, 'VPC', {
58
- isDefault: true
59
- })
60
-
61
- const securityGroup = new SecurityGroup(this, 'SecurityGroup', {
62
- vpc,
63
- allowAllOutbound: true
64
- })
49
+ const vpc = Vpc.fromLookup(this, 'VPC', { isDefault: true })
50
+ const securityGroup = new SecurityGroup(this, 'SecurityGroup', { vpc, allowAllOutbound: true })
65
51
 
66
52
  securityGroup.addIngressRule(
67
53
  Peer.anyIpv4(),
@@ -81,6 +67,16 @@ class ContainerServer extends Construct {
81
67
  'allow HTTPS traffic from anywhere'
82
68
  )
83
69
 
70
+ const deploymentQueue = new Queue(this, 'DeploymentQueue', {
71
+ queueName: `${props.config.awsDeploymentSqsName}`,
72
+ receiveMessageWaitTime: Duration.seconds(20)
73
+ })
74
+
75
+ const platformConfigDeployment = new BucketDeployment(this, 'PlatformConfigDeployment', {
76
+ sources: [Source.jsonData('platform-config.json', props.config)],
77
+ destinationBucket: props.bucket
78
+ })
79
+
84
80
  const role = new Role(this, 'role', {
85
81
  assumedBy: new ServicePrincipal('ec2.amazonaws.com')
86
82
  })
@@ -95,7 +91,7 @@ class ContainerServer extends Construct {
95
91
  'route53:ChangeResourceRecordSets'
96
92
  ],
97
93
  resources: [
98
- `arn:aws:route53:::hostedzone/${hostedZone.hostedZoneId}`,
94
+ `arn:aws:route53:::hostedzone/*`,
99
95
  'arn:aws:route53:::change/*'
100
96
  ]
101
97
  }),
@@ -127,7 +123,7 @@ class ContainerServer extends Construct {
127
123
  )
128
124
 
129
125
  userData.addS3DownloadCommand({
130
- bucket: props.platformConfigBucket,
126
+ bucket: props.bucket,
131
127
  bucketKey: 'platform-config.json',
132
128
  localFile: '/home/ubuntu/platform-config.json'
133
129
  })
@@ -152,25 +148,15 @@ class ContainerServer extends Construct {
152
148
  machineImage: new GenericLinuxImage({
153
149
  [SupportedRegions.North]: InstanceImages.UBUNTU
154
150
  }),
155
- keyName: props.platformConfig.awsKeyPairName
151
+ keyName: props.config.awsKeyPairName
156
152
  })
157
153
 
158
- props.platformConfigBucket.grantRead(ec2Instance, '*')
159
- props.deploymentQueue.grant(ec2Instance, '*')
160
-
161
- // const hostedZone = !!props.createNewHostedZone
162
- // ? new PublicHostedZone(this, 'HostedZone', { zoneName: props.domain })
163
- // : HostedZone.fromLookup(this, 'HostedZone', { domainName: props.domain });
154
+ props.bucket.grantRead(ec2Instance, '*')
155
+ deploymentQueue.grant(ec2Instance, '*')
164
156
 
165
- // eslint-disable-next-line no-new
166
- new ARecord(this, 'WildcardRecord', {
167
- zone: hostedZone,
168
- recordName: `*.${props.platformConfig.activeEnvironment}.${props.platformConfig.domain}`,
169
- target: RecordTarget.fromIpAddresses(ec2Instance.instancePublicIp)
170
- })
157
+ this.instancePublicIp = ec2Instance.instancePublicIp
171
158
 
172
- // eslint-disable-next-line no-new
173
- new CfnOutput(this, 'Intance Ip', {
159
+ new CfnOutput(this, 'Instance Ip', {
174
160
  value: ec2Instance.instancePublicIp,
175
161
  description: 'Public ip of the ec2 instance',
176
162
  exportName: 'instanceIp'
@@ -180,5 +166,5 @@ class ContainerServer extends Construct {
180
166
  }
181
167
 
182
168
  module.exports = {
183
- ContainerServer
169
+ ContainerDeploymentTarget
184
170
  }
@@ -0,0 +1,3 @@
1
+ const { ContainerDeploymentTarget } = require('./container-deployment-target')
2
+
3
+ module.exports = { ContainerDeploymentTarget }
@@ -0,0 +1,46 @@
1
+ /* eslint-disable no-new */
2
+ const { nanoid } = require('nanoid')
3
+ const { Stack, Duration, RemovalPolicy } = require('aws-cdk-lib')
4
+ const { HostedZone, ARecord, RecordTarget } = require('aws-cdk-lib/aws-route53')
5
+ const { Bucket, BucketEncryption, BlockPublicAccess } = require('aws-cdk-lib/aws-s3')
6
+ const { ContainerDeploymentTarget } = require('./container-deployment-target')
7
+
8
+ /**
9
+ * @class
10
+ */
11
+ class DeploymentTargetStack extends Stack {
12
+ constructor(scope, id, props) {
13
+ super(scope, id, props)
14
+
15
+ if (!props?.config) {
16
+ throw ('[DeploymentTargetStack] No template provided')
17
+ }
18
+
19
+ const bucketId = nanoid().toLowerCase().replaceAll('_', '').replaceAll('-', '')
20
+ const bucketName = `${props.config.platformName}-${bucketId}`
21
+
22
+ // TODO: this should probably not be destroyed....
23
+ const staticDeploymentTarget =
24
+ new Bucket(this, 'StaticDeploymentTarget', {
25
+ bucketName: bucketName,
26
+ blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
27
+ encryption: BucketEncryption.S3_MANAGED,
28
+ removalPolicy: RemovalPolicy.DESTROY,
29
+ autoDeleteObjects: true
30
+ })
31
+
32
+ // TODO: add persistant storage
33
+ const containerDeploymentTarget =
34
+ new ContainerDeploymentTarget(this, 'ContainerDeploymentTarget', {
35
+ config: props.config,
36
+ bucket: staticDeploymentTarget
37
+ })
38
+
39
+ this.containerDeploymentTargetPublicIp = containerDeploymentTarget.instancePublicIp
40
+
41
+ }
42
+ }
43
+
44
+ module.exports = {
45
+ DeploymentTargetStack
46
+ }
@@ -0,0 +1,61 @@
1
+ const { Stack } = require('aws-cdk-lib')
2
+ const { HostedZone, ARecord, RecordTarget } = require('aws-cdk-lib/aws-route53')
3
+ const { SupportedDeploymentTypes, SupportedEnvironments } = require('../config')
4
+ const { DeploymentTemplateService } = require('../template')
5
+
6
+ /**
7
+ * DnsStackProps
8
+ * @namespace DnsStack
9
+ * @typedef {Object} DnsStackProps
10
+ * @property {PlatformConfig} config - platform config
11
+ * @property {PlatformConfig} deployments - deployment templates[]
12
+ * @property {string} containerDeploymentTargetPublicIp - public ip address of ec2 instance that hosts our containerss
13
+ */
14
+
15
+ const InstanceImages = {
16
+ UBUNTU: 'ami-092cce4a19b438926'
17
+ }
18
+
19
+ /**
20
+ * @class
21
+ */
22
+ class DnsStack extends Stack {
23
+
24
+ /**
25
+ * @param {object} scope - scope
26
+ * @param {string} id - id
27
+ * @param {ContainerServerProps} props - ContainerServerProps
28
+ */
29
+ constructor(scope, id, props) {
30
+ super(scope, id, props)
31
+
32
+ if (props.config.environmentType === SupportedEnvironments.PROD) {
33
+ // const isDomainForCorrectEnvironment = !Object.values(SupportedEnvironments)
34
+ // .find(env => deployment.domain.includes(env))
35
+ return
36
+ }
37
+
38
+ const containerDeployments = DeploymentTemplateService
39
+ .getContainerDeploymentsForEnvironmentType(props.config.environmentType, props.deployments)
40
+
41
+ DeploymentTemplateService
42
+ .groupDeploymentDomainsByRootDomain(containerDeployments)
43
+ .forEach((domains, rootDomain) => {
44
+ const hostedZone = new HostedZone(this, rootDomain, { zoneName: rootDomain })
45
+
46
+ domains.forEach(domain => {
47
+ new ARecord(this, domain, {
48
+ zone: hostedZone,
49
+ recordName: domain,
50
+ target: RecordTarget.fromIpAddresses(props.containerDeploymentTargetPublicIp)
51
+ })
52
+ })
53
+
54
+ })
55
+
56
+ }
57
+ }
58
+
59
+ module.exports = {
60
+ DnsStack
61
+ }
@@ -11,7 +11,7 @@ const {
11
11
  /**
12
12
  * @class
13
13
  */
14
- class EstablishTrustStack extends Stack {
14
+ class TrustCiStack extends Stack {
15
15
  /**
16
16
  * Establishes trust between GithHub and Amazon Web Services.
17
17
  * This is needed so that we can interact with Amazon Web Services through
@@ -50,7 +50,7 @@ class EstablishTrustStack extends Stack {
50
50
  statements: [
51
51
  new PolicyStatement({
52
52
  effect: Effect.ALLOW,
53
- actions: ['sts:AssumeRole'],
53
+ actions: ['sts:AssumeRole', 'sqs:SendMessage'],
54
54
  resources: [
55
55
  `arn:aws:iam::${props.config.awsAccountId}:role/cdk-*`,
56
56
  `arn:aws:sqs:${props.config.awsRegion}:${props.config.awsAccountId}:*`
@@ -64,5 +64,5 @@ class EstablishTrustStack extends Stack {
64
64
  }
65
65
 
66
66
  module.exports = {
67
- EstablishTrustStack
67
+ TrustCiStack
68
68
  }
@@ -1,6 +1,5 @@
1
1
  const { CaddyService } = require('../caddy')
2
2
  const { DockerService } = require('../docker')
3
- const { RestApiService } = require('./rest-api')
4
3
 
5
4
  const { PlatformTemplateService } = require('../template')
6
5
  const { PlatformConfigService } = require('../config')
@@ -18,7 +17,6 @@ class PlatformServerService {
18
17
  PlatformTemplateService.readFromFile(platformTemplatesFilePath).then(([firstPlatformTemplateFound]) => {
19
18
  const platformConfig = PlatformConfigService.from(firstPlatformTemplateFound)
20
19
 
21
- RestApiService.start(platformConfig)
22
20
  DockerService.createDockerNetworkForContainerManagerServer(platformConfig)
23
21
  .then(() => DockerService.startDefaultContainers(platformConfig))
24
22
  CaddyService.applyDefaultConfig(platformConfig)
@@ -27,7 +25,7 @@ class PlatformServerService {
27
25
  platformConfig,
28
26
  deploymentRequest => {
29
27
  DockerService.deploy(platformConfig, deploymentRequest)
30
- .then(() => CaddyService.addDeployment(platformConfig, deploymentRequest))
28
+ .then(() => CaddyService.applyConfig(deploymentRequest.caddyConfig))
31
29
  return Promise.resolve()
32
30
  }
33
31
  )
@@ -0,0 +1,86 @@
1
+ const { glob } = require('glob')
2
+ const { readFileSync } = require('fs')
3
+ const { logError, logInfo } = require('../log')
4
+ const { SupportedDeploymentTypes, SupportedEnvironments } = require('../config')
5
+
6
+ /**
7
+ * Deployment template definition
8
+ * @typedef {Object} DeploymentTemplate
9
+ * @property {string} type - CONTAINER | STATIC
10
+ * @property {string} domain - example.com
11
+ * @property {string} targetDeploymentPlatform - name of platform to deploy to
12
+ *
13
+ * @property {string} image - name of image to be deployed
14
+ * @property {string} registry - registry where the image is hosted
15
+ * @property {string=} hostPort - host port
16
+ * @property {string=} containerPort - port that the container exposes
17
+ */
18
+
19
+ /**
20
+ * Utility class that helps you read and validate platfor templates from file system
21
+ * @class
22
+ */
23
+ class DeploymentTemplateService {
24
+
25
+ /**
26
+ * Read and and group deployments by targetDeploymentPlatform
27
+ *
28
+ * @param {string} blob - File path to platform templates json
29
+ */
30
+ static readOssyFiles(blob) {
31
+ return glob(blob, { ignore: 'node_modules/**' })
32
+ .then(filePaths => filePaths
33
+ .map(path => readFileSync(path, 'utf-8'))
34
+ .map(json => JSON.parse(json))
35
+ .flatMap(ossyFileContent => ossyFileContent.deployments)
36
+ .reduce((deploymentsMap, deployment) => {
37
+
38
+ if (!!deploymentsMap[deployment.targetDeploymentPlatform]) {
39
+ return {
40
+ ...deploymentsMap,
41
+ [deployment.targetDeploymentPlatform]: [
42
+ ...deploymentsMap[deployment.targetDeploymentPlatform],
43
+ deployment
44
+ ]
45
+ }
46
+ }
47
+
48
+ return {
49
+ ...deploymentsMap,
50
+ [deployment.targetDeploymentPlatform]: [deployment]
51
+ }
52
+
53
+ }, {})
54
+ )
55
+ }
56
+
57
+ static getContainerDeploymentsForEnvironmentType(environmentType, deployments) {
58
+ return deployments
59
+ .filter(deployment => {
60
+ const isContainerDeployment = deployment.type === SupportedDeploymentTypes.Container
61
+ const isDomainForCorrectEnvironment = deployment.domain.includes(environmentType)
62
+ return isContainerDeployment && isDomainForCorrectEnvironment
63
+ })
64
+ }
65
+
66
+ static groupDeploymentDomainsByRootDomain(deployments) {
67
+ return deployments
68
+ .map(deployment => deployment.domain)
69
+ .reduce((domainGroups, domain) => {
70
+ const [topLevelDomain, domainName] = domain.split('.').reverse()
71
+ const rootDomain = `${domainName}.${topLevelDomain}`
72
+
73
+ domainGroups.has(rootDomain)
74
+ ? domainGroups.set(rootDomain, [...domainGroups.get(rootDomain), domain])
75
+ : domainGroups.set(rootDomain, [domain])
76
+
77
+ return domainGroups
78
+ }, new Map())
79
+ }
80
+
81
+
82
+ }
83
+
84
+ module.exports = {
85
+ DeploymentTemplateService
86
+ }
@@ -1 +1,4 @@
1
- module.exports = require('./platform-template')
1
+ module.exports = {
2
+ ...require('./deployment-template'),
3
+ ...require('./platform-template')
4
+ }
@@ -7,8 +7,7 @@ const { logError, logInfo } = require('../log')
7
7
  * @typedef {Object} PlatformTemplate
8
8
  * @property {string} platformName - Name of platform
9
9
  * @property {string} domain - example.com
10
- * @property {string[]} supportedDeploymentTypes - container
11
- * @property {string[]} supportedEnvironments - qa
10
+ * @property {string} environmentType - local, test, qa, prod
12
11
  *
13
12
  * @property {string} awsAccountId - Aws account id
14
13
  * @property {string=} awsRegion - ?
@@ -16,10 +15,6 @@ const { logError, logInfo } = require('../log')
16
15
  * @property {string} awsRoleToAssume - ?
17
16
  * @property {string=} awsDeploymentSqsArn - ?
18
17
  *
19
- * @property {string=} ciSubDomain - ?
20
- * @property {string|mumber=} ciInternalServerPort - ? | number;
21
- * @property {string=} ciServerName - ?
22
- * @property {string=} ciDockerNetworkName - ?
23
18
  * @property {string} ciGithubActionsRepo - organisation/repoName
24
19
  */
25
20
 
@@ -1,21 +0,0 @@
1
- const { CaddyService } = require('./caddy')
2
-
3
- const config = {
4
- activeEnvironment: 'prod',
5
- domain: 'localhost',
6
- ciServerName: 'ossy',
7
- ciSubDomain: 'ci',
8
- ciInternalServerPort: '3000'
9
- }
10
-
11
- const deploymentRequest = {
12
- subdomain: 'cc',
13
- hostPort: '3000'
14
- }
15
-
16
- CaddyService.applyDefaultConfig(config)
17
-
18
- // CaddyService.addDeployment(config, deploymentRequest)
19
-
20
- // CaddyService.fetchServerConfig(config)
21
- // .then(console.log)
@@ -1,3 +0,0 @@
1
- const { ContainerServer } = require('./container-server')
2
-
3
- module.exports = { ContainerServer }
@@ -1,58 +0,0 @@
1
- /* eslint-disable no-new */
2
- const { Stack, Duration, RemovalPolicy } = require('aws-cdk-lib')
3
- const { Queue } = require('aws-cdk-lib/aws-sqs')
4
- const { Bucket, BucketEncryption, BlockPublicAccess } = require('aws-cdk-lib/aws-s3')
5
- const { Source, BucketDeployment } = require('aws-cdk-lib/aws-s3-deployment')
6
- const { nanoid } = require('nanoid')
7
-
8
- const { ContainerServer } = require('./container-server')
9
-
10
- const { SupportedDeploymentTypes } = require('../config')
11
-
12
- /**
13
- * @class
14
- */
15
- class PlatformStack extends Stack {
16
- constructor(scope, id, props) {
17
- super(scope, id, props)
18
-
19
- if (!props?.config) {
20
- throw ('[PlatformStack] No template config provided')
21
- }
22
-
23
- const isContainerDeploymentsEnabled = props.config.supportedDeploymentTypes.includes(SupportedDeploymentTypes.Container)
24
-
25
- if (isContainerDeploymentsEnabled) {
26
-
27
- const deploymentQueue = new Queue(this, 'DeploymentQueue', {
28
- queueName: `${props.config.platformName}-${props.config.activeEnvironment}`,
29
- receiveMessageWaitTime: Duration.seconds(20)
30
- })
31
-
32
- const platformConfigBucket = new Bucket(this, 'PlatformConfig', {
33
- bucketName: `${props.config.platformName}-${props.config.activeEnvironment}-${nanoid().toLowerCase().replaceAll('_', '').replaceAll('-', '')}`,
34
- blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
35
- encryption: BucketEncryption.S3_MANAGED,
36
- removalPolicy: RemovalPolicy.DESTROY,
37
- autoDeleteObjects: true
38
- })
39
-
40
- const platformConfigDeployment = new BucketDeployment(this, 'PlatformConfigDeployment', {
41
- sources: [Source.jsonData('platform-config.json', props.config)],
42
- destinationBucket: platformConfigBucket
43
- })
44
-
45
- new ContainerServer(this, 'ContainerServer', {
46
- platformConfig: props.config,
47
- platformConfigBucket: platformConfigDeployment.deployedBucket,
48
- deploymentQueue: deploymentQueue
49
- })
50
-
51
- }
52
-
53
- }
54
- }
55
-
56
- module.exports = {
57
- PlatformStack
58
- }
@@ -1,10 +0,0 @@
1
- // const { PlatformServerService } = require('./platform-server')
2
- const { RestApiService } = require('./rest-api')
3
-
4
- // PlatformServerService.start({
5
- //
6
- // })
7
-
8
- RestApiService.start({
9
- ciInternalServerPort: 3000
10
- })
@@ -1,31 +0,0 @@
1
- const express = require('express')
2
- const { logInfo } = require('../log')
3
-
4
- /**
5
- * @class
6
- */
7
- class RestApiService {
8
-
9
- static start(platformConfig) {
10
- const server = express()
11
-
12
- server.use(express.json())
13
-
14
- server.get('/', (req, res) => {
15
- res.redirect('/status')
16
- })
17
-
18
- server.get('/status', (req, res) => {
19
- res.send('Server is live')
20
- })
21
-
22
- server.listen(platformConfig.ciInternalServerPort, () => {
23
- logInfo({ message: `[RestApiService] API is live on port ${platformConfig.ciInternalServerPort}`})
24
- })
25
- }
26
-
27
- }
28
-
29
- module.exports = {
30
- RestApiService
31
- }