-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] feat(infra): First pass at microservice TS project [experiment] #210
Changes from all commits
e5edf2b
7b799ba
53c0160
5b1b142
5f33089
9fb1ba1
daaa511
63db09d
6e5c52d
eb50b33
213d19b
5d9b3d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
import { Component, SampleFile } from 'projen'; | ||
import merge from 'ts-deepmerge'; | ||
import { clickupTs } from './clickup-ts'; | ||
import { renovateWorkflow } from './renovate-workflow'; | ||
import { ecsServiceCDWorkflow } from './workflows/ecs-service-cd'; | ||
import { ecsServiceCIWorkflow } from './workflows/ecs-service-ci'; | ||
import { ecsServiceDeployWorkflow } from './workflows/ecs-service-deploy'; | ||
|
||
export module clickupEcsService { | ||
export const defaults: Omit<ClickUpTypeScriptEcsServiceProjectOptions, 'name' | 'serviceName'> = { | ||
...clickupTs.defaults, | ||
workflowNodeVersion: '16', | ||
}; | ||
|
||
export interface ClickUpTypeScriptEcsServiceProjectOptions extends clickupTs.ClickUpTypeScriptProjectOptions { | ||
readonly deployWorkflowOptions?: ecsServiceDeployWorkflow.EcsServiceDeployOptionsConfig; | ||
readonly serviceName: string; | ||
readonly serviceHealthcheckPath?: string; | ||
} | ||
|
||
export class ClickUpTypeScriptEcsServiceProject extends clickupTs.ClickUpTypeScriptProject { | ||
readonly workflowNodeVersion: string; | ||
readonly serviceName: string; | ||
readonly serviceHealthcheckPath: string; | ||
|
||
constructor(options: ClickUpTypeScriptEcsServiceProjectOptions) { | ||
const serviceHealthcheckPath = options.serviceHealthcheckPath ?? `${options.serviceName}/health`; | ||
let mergedOptions = merge( | ||
defaults, | ||
{ | ||
deps: clickupTs.deps, | ||
deployWorkflowOptions: { | ||
deployEnvs: [ecsServiceDeployWorkflow.ClickUpHarnessEnv.QA], | ||
serviceHealthcheckPath, | ||
}, | ||
}, | ||
options, | ||
{ | ||
// Disable projen's built-in docgen class | ||
docgen: undefined, | ||
name: clickupTs.normalizeName(options.name), | ||
renovatebotOptions: renovateWorkflow.getRenovateOptions(options.renovateOptionsConfig), | ||
}, | ||
); | ||
super(mergedOptions); | ||
this.workflowNodeVersion = mergedOptions.workflowNodeVersion!; | ||
this.serviceName = options.serviceName; | ||
this.serviceHealthcheckPath = serviceHealthcheckPath; | ||
|
||
new AppSampleCode(this); | ||
|
||
ecsServiceCIWorkflow.createContinuousIntegrationWorkflow(this, { | ||
...mergedOptions, | ||
nodeVersion: this.workflowNodeVersion, | ||
}); | ||
|
||
ecsServiceCDWorkflow.createContinuousDeliveryWorkflow(this, { | ||
...mergedOptions, | ||
...mergedOptions.deployWorkflowOptions!, | ||
nodeVersion: this.workflowNodeVersion, | ||
}); | ||
} | ||
} | ||
|
||
class AppSampleCode extends Component { | ||
constructor(project: ClickUpTypeScriptEcsServiceProject) { | ||
super(project); | ||
|
||
new SampleFile(project, 'Dockerfile', { | ||
contents: this.renderSample('Dockerfile.sample'), | ||
}); | ||
|
||
new SampleFile(project, 'scripts/docker-healthcheck.sh', { | ||
contents: this.renderSample('docker-healthcheck.sh.sample', { | ||
'<%SERVICE_HEALTHCHECK_PATH%>': project.serviceHealthcheckPath, | ||
}), | ||
}); | ||
|
||
new SampleFile(project, 'scripts/docker-build.sh', { | ||
contents: this.renderSample('docker-build.sh.sample', { | ||
'<%SERVICE_NAME%>': project.serviceName, | ||
}), | ||
}); | ||
} | ||
|
||
private getSample(fileName: string): string { | ||
return fs.readFileSync(path.join(__dirname, 'samples', fileName), 'utf-8'); | ||
} | ||
|
||
private renderSample(sampleFileName: string, vars?: { [toReplace: string]: string }): string { | ||
let contents = this.getSample(sampleFileName); | ||
|
||
if (vars) { | ||
const toReplace = Object.keys(vars); | ||
toReplace.map((key) => { | ||
contents = contents.replace(new RegExp(key, 'g'), vars[key]); | ||
}); | ||
} | ||
return contents; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
FROM amazonlinux:latest@sha256:66d22a2b8749283aa0764e6983fbb8bf8c143511cf16c9773598acca2a1cd6d2 | ||
|
||
COPY .nvmrc ./ | ||
|
||
# Download and install node | ||
RUN curl -fsSL https://rpm.nodesource.com/setup_$(cat .nvmrc | cut -d. -f1).x | bash - \ | ||
&& yum clean metadata \ | ||
&& yum -y update-minimal \ | ||
&& yum -y install nodejs-$(cat .nvmrc)-1nodesource \ | ||
&& yum clean all \ | ||
&& rm -rf /var/cache/yum \ | ||
# smoke tests | ||
&& node --version \ | ||
&& npm --version | ||
|
||
# Install other dependencies | ||
RUN \ | ||
yum -y update-minimal \ | ||
&& yum -y install \ | ||
# Common util | ||
curl \ | ||
# To make pnpm install work | ||
gcc-c++ make python3 \ | ||
# Runtime debugging | ||
psmisc \ | ||
&& yum clean all \ | ||
&& rm -rf /var/cache/yum | ||
|
||
RUN node --version | ||
|
||
WORKDIR /app | ||
|
||
# Files required by pnpm install | ||
COPY .npmrc package.json pnpm-lock.yaml ./ | ||
COPY ./patches ./patches | ||
|
||
# Install package manager | ||
RUN corepack enable | ||
|
||
# Forces pnpm to only install production dependencies and prevents pnpm post install error from husky | ||
ARG NODE_ENV=production | ||
|
||
# Mount the npmrc file containing the npm token with permission to read private packages | ||
RUN --mount=type=secret,id=npmrc,dst=/root/.npmrc npm run ci:install | ||
|
||
# Copy your dist and source files here, set NODE_CONFIG_DIR, etc. | ||
COPY ./dist/ . | ||
|
||
COPY ./dist/config ./config/app/ | ||
COPY ./src/config ./config/ | ||
ENV NODE_CONFIG_DIR=./config | ||
|
||
|
||
ARG VERSION=0.0.1 | ||
ENV VERSION=${VERSION} | ||
|
||
ENV PORT=80 | ||
EXPOSE 80 | ||
|
||
# Tell the app to listen on this port. | ||
ENV DOCKER_APP_PORT=8080 | ||
|
||
# Setup docker healthcheck | ||
COPY ./scripts/docker-healthcheck.sh ./ | ||
HEALTHCHECK --start-period=300s CMD ./docker-healthcheck.sh | ||
|
||
CMD node ./main.js |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#!/bin/bash | ||
set -euox pipefail | ||
|
||
cd $(dirname "${BASH_SOURCE[0]}") | ||
cd ../../ | ||
|
||
pnpm ci:install | ||
pnpm exec nx build <%SERVICE_NAME%> | ||
|
||
# To use `--secrets` it requires BuildKit. Enabling this. | ||
# https://docs.docker.com/develop/develop-images/build_enhancements/#to-enable-buildkit-builds | ||
export DOCKER_BUILDKIT=1 | ||
|
||
exec docker build --secret id=npmrc,src=$HOME/.npmrc -t <%SERVICE_NAME%>:1.0 -f ./Dockerfile . $@ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#!/bin/bash | ||
set -e -o pipefail | ||
cd $(dirname "${BASH_SOURCE[0]}") | ||
|
||
# Perform basic healthcheck: Check that the express service is running and can | ||
# response on basic http request. We are not checking the full health and only | ||
# checking that node is up and running. We must check the response status AND | ||
# the content, since our backend returns a gif for any non-defined URL. | ||
|
||
# TODO: Do a curl against your service, check for thumbs up like so: | ||
curl --fail http://localhost:80/<%SERVICE_HEALTHCHECK_PATH%> | grep "Thumbs up" || exit 1 | ||
|
||
# TODO: Set up worker-type healthchecks in case ECS does not have load balancer | ||
# attached to it which performs full healthcheck. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love/hate the use of SampleFile. Does it make sense to go managed instead? What level of change is reasonable to expect / support?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the Dockerfile and build scripts likely need to be samples because otherwise there's little-to-no customization that can be added easily. I don't necessarily want additional Dockerfile steps submitted through
.projenrc.ts
. In fact ideally there would be a base image that the sampleDockerfile
would pull from.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for docker-healthcheck...that could almost certainly be managed. Can actually be a mechanism of enforcing a particular healthcheck pattern.