Skip to content

Commit

Permalink
Add kubernetes deployment to Express generator
Browse files Browse the repository at this point in the history
  • Loading branch information
jvasseur committed Jan 27, 2022
1 parent b5b364b commit c66007c
Show file tree
Hide file tree
Showing 22 changed files with 363 additions and 11 deletions.
32 changes: 32 additions & 0 deletions generators/express/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,38 @@ describe('When running the generator', () => {
});
});

describe('When running the generator with kubernetes deployment', () => {
let root: string;

beforeAll(async () => {
const result = await helpers.run(path.resolve(__dirname, '../root'))
.withPrompts({ deployment: 'kubernetes' });

root = result.cwd;

await helpers.run(__dirname)
.cd(root)
.withArguments(['test']);
});

afterAll(async () => {
await fs.promises.rm(root, { recursive: true });
});

test('It generates a valid terraform config', async () => {
const cwd = path.join(root, 'environments', 'staging');

await execa('terraform', ['init', '--backend=false'], { cwd });
await execa('terraform', ['validate'], { cwd });
});

test('It generates a valid helm chart', async () => {
const cwd = path.join(root, 'modules', 'deployment', 'chart');

await execa('helm', ['lint'], { cwd });
});
});

describe('When running the generator with the path option', () => {
let root: string;

Expand Down
29 changes: 27 additions & 2 deletions generators/express/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import PackageGenerator from '../../utils/PackageGenerator';
import varify from '../../utils/varify';
import { DeploymentChoice } from '../root';

class ExpressGenerator extends PackageGenerator {
Expand All @@ -9,6 +10,7 @@ class ExpressGenerator extends PackageGenerator {
}

writing(): void {
const projectName = this.config.get('projectName');
const { packageName, packagePath } = this.options;

this.renderTemplate('base', packagePath);
Expand All @@ -21,12 +23,35 @@ class ExpressGenerator extends PackageGenerator {

switch (this.config.get('deployment')) {
case DeploymentChoice.Ansible:
this.configureAnsible('ansible', {
this.configureAnsible('deployment/ansible', {
repositoryName: this.config.get('repositoryName'),
});
break;
case DeploymentChoice.Kubernetes:
throw new Error('The express generator is not yet compatible with kubernetes deployment');
// Add files required for docker build
this.renderTemplate('deployment/kubernetes/docker', packagePath);

// Update chart
this.configureChart('deployment/kubernetes/chart');

this.replaceDestination(
'modules/deployment/chart/values.yaml',
/$/s,
`\n${varify(packageName)}:\n image:\n tag: latest\n digest: ~\n sentry:\n dsn: ~\n database:\n host: ~\n port: ~\n user: ~\n password: ~\n name: ~\n`,
);

this.writeTerraformVariable(`${varify(packageName)}_sentry_dsn`, 'string', '"" # TODO Add sentry DSN here');
this.writeTerraformVariable(`${varify(packageName)}_image_tag`, 'string', '"develop"');

this.replaceDestination(
'modules/deployment/release.tf',
/$/s,
`\ndata "docker_registry_image" "${varify(packageName)}" {\n name = "\${var.registry}/${projectName}-${packageName}:\${var.${varify(packageName)}_image_tag}"\n}\n`,
);

this.writeReleaseVariable(`${varify(packageName)}.image.digest`, `data.docker_registry_image.${varify(packageName)}.sha256_digest`);
this.writeReleaseVariable(`${varify(packageName)}.sentry.dsn`, `var.${varify(packageName)}_sentry_dsn`);
break;
}

this.configureScripts('script');
Expand Down
58 changes: 58 additions & 0 deletions generators/express/templates/circleci.yaml.ejs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
version: '2.1'

orbs:
<% if (deployment === 'kubernetes') { %>
docker: circleci/docker@2.0.1
<% } %>
node: circleci/[email protected]

executors:
Expand Down Expand Up @@ -44,6 +47,7 @@ jobs:
- run:
command: yarn test

<% if (deployment === 'ansible') { %>
<%= packageName %>-build:
executor: node
working_directory: ~/project/<%= packagePath %>
Expand Down Expand Up @@ -97,6 +101,38 @@ jobs:
- run: sentry-cli releases new <%= projectName %>-<%= packageName %>@${CIRCLE_SHA1}
- run: sentry-cli releases files <%= projectName %>-<%= packageName %>@${CIRCLE_SHA1} upload-sourcemaps <%= packagePath %>/dist
- run: sentry-cli releases finalize <%= projectName %>-<%= packageName %>@${CIRCLE_SHA1}
<% } %>
<% if (deployment === 'kubernetes') { %>
<%= packageName %>-build:
executor: docker/docker
steps:
- setup_remote_docker
- checkout
- docker/build:
image: <%= projectName %>-<%= packageName %>
tag: << pipeline.git.branch >>
path: <%= packagePath %>
docker-context: <%= packagePath %>
extra_build_args: '--build-arg VERSION=$(git log --max-count 1 --pretty=format:%H "<%= packagePath %>")'
<%= packageName %>-build-and-push:
executor: docker/docker
steps:
- setup_remote_docker
- checkout
- docker/build:
registry: $DOCKER_REGISTRY
image: <%= projectName %>-<%= packageName %>
tag: << pipeline.git.branch >>
path: <%= packagePath %>
docker-context: <%= packagePath %>
extra_build_args: '--build-arg VERSION=$(git log --max-count 1 --pretty=format:%H "<%= packagePath %>")'
- docker/check:
registry: $DOCKER_REGISTRY
- docker/push:
registry: $DOCKER_REGISTRY
image: <%= projectName %>-<%= packageName %>
tag: << pipeline.git.branch >>
<% } %>

workflows:
version: '2'
Expand All @@ -109,6 +145,7 @@ workflows:
- <%= packageName %>-test:
requires:
- <%= packageName %>-lint
<% if (deployment === 'ansible') { %>
- <%= packageName %>-build:
requires:
- <%= packageName %>-test
Expand All @@ -124,3 +161,24 @@ workflows:
only:
- develop
- main
<% } %>
<% if (deployment === 'kubernetes') { %>
- <%= packageName %>-build-and-push:
context:
- docker-registry
requires:
- <%= packageName %>-test
filters:
branches:
only:
- develop
- main
- <%= packageName %>-build:
requires:
- <%= packageName %>-test
filters:
branches:
ignore:
- develop
- main
<% } %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{{- define "<%= projectName %>.<%= packageName %>.image" }}
{{- .Values.registry }}/<%= projectName %>-<%= packageName %>
{{- if .Values.<%= varify(packageName) %>.image.digest -}}
@{{ .Values.<%= varify(packageName) %>.image.digest }}
{{- else -}}
:{{ .Values.<%= varify(packageName) %>.image.tag }}
{{- end -}}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "<%= projectName %>.fullname" . }}-<%= packageName %>-database
labels:
app.kubernetes.io/component: <%= packageName %>
{{- include "<%= projectName %>.labels" . | nindent 4 }}
type: Opaque
data:
url: {{ (print "postgresql://" .Values.<%= varify(packageName) %>.database.user ":" (.Values.<%= varify(packageName) %>.database.password | urlquery) "@" .Values.<%= varify(packageName) %>.database.host ":" .Values.<%= varify(packageName) %>.database.port "/" .Values.<%= varify(packageName) %>.database.name) | b64enc }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "<%= projectName %>.fullname" . }}-<%= packageName %>
labels:
app.kubernetes.io/component: <%= packageName %>
{{- include "<%= projectName %>.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: <%= packageName %>
{{- include "<%= projectName %>.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/database-secret: {{ include (print $.Template.BasePath "/<%= packageName %>-database-secret.yaml") . | sha256sum }}
labels:
app.kubernetes.io/component: <%= packageName %>
{{- include "<%= projectName %>.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: node
image: {{ include "<%= projectName %>.<%= packageName %>.image" . }}
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "<%= projectName %>.fullname" . }}-<%= packageName %>-database
key: url
- name: ENVIRONMENT_NAME
value: {{ .Values.environment_name }}
- name: SENTRY_DSN
value: {{ .Values.<%= varify(packageName) %>.sentry.dsn }}
ports:
- name: http
containerPort: 3000
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
imagePullSecrets:
- name: registry-secret
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "<%= projectName %>.fullname" . }}-<%= packageName %>
labels:
app.kubernetes.io/component: <%= packageName %>
{{- include "<%= projectName %>.labels" . | nindent 4 }}
{{- if .Values.basic_auth_password }}
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: {{ include "<%= projectName %>.fullname" . }}-basic-auth
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required'
{{- end }}
spec:
rules:
- host: {{ .Values.host }}
http:
paths:
- path: <%= httpPath %>
pathType: Prefix
backend:
service:
name: {{ include "<%= projectName %>.fullname" . }}-<%= packageName %>
port:
number: 80
tls:
- hosts:
- {{ .Values.host }}
secretName: {{ .Values.certificate | default (print (include "<%= projectName %>.fullname" .) "-cert") }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "<%= projectName %>.fullname" . }}-<%= packageName %>-migrate
labels:
app.kubernetes.io/component: <%= packageName %>
{{- include "<%= projectName %>.labels" . | nindent 4 }}
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
spec:
template:
metadata:
annotations:
checksum/database-secret: {{ include (print $.Template.BasePath "/<%= packageName %>-database-secret.yaml") . | sha256sum }}
spec:
containers:
- name: node
image: {{ include "<%= projectName %>.<%= packageName %>.image" . }}
command:
- typeorm
- migration:run
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "<%= projectName %>.fullname" . }}-<%= packageName %>-database
key: url
- name: ENVIRONMENT_NAME
value: {{ .Values.environment_name }}
- name: SENTRY_DSN
value: {{ .Values.<%= varify(packageName) %>.sentry.dsn }}
restartPolicy: Never
imagePullSecrets:
- name: registry-secret
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "<%= projectName %>.fullname" . }}-<%= packageName %>
labels:
app.kubernetes.io/component: <%= packageName %>
{{- include "<%= projectName %>.labels" . | nindent 4 }}
spec:
selector:
app.kubernetes.io/component: <%= packageName %>
{{- include "<%= projectName %>.selectorLabels" . | nindent 4 }}
ports:
- protocol: TCP
port: 80
targetPort: 3000
name: http
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/dist/
/node_modules/
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Base image for all steps
FROM node:16.13.1-slim as base

WORKDIR /srv/<%= packageName %>/

# Dev dependencies
FROM base as devDependencies

COPY package.json .
COPY yarn.lock .

RUN yarn install --frozen-lockfile

# Prod dependencies
FROM devDependencies as dependencies

RUN yarn install --frozen-lockfile --production

# Build
FROM devDependencies as build

COPY tsconfig.json .
COPY src ./src

ARG VERSION
RUN echo "export default '$VERSION';" > src/version.ts

RUN yarn build

# Final image
FROM base

COPY ormconfig-dist.js ./ormconfig.js
COPY --from=dependencies /srv/<%= packageName %>/node_modules /srv/<%= packageName %>/node_modules
COPY --from=build /srv/<%= packageName %>/dist /srv/<%= packageName %>/dist

USER node

ENV NODE_ENV="production"
ENV PATH="/srv/<%= packageName %>/node_modules/.bin:${PATH}"

EXPOSE 3000

CMD ["node", "dist"]
Loading

0 comments on commit c66007c

Please sign in to comment.