Skip to content

Commit 327258e

Browse files
committed
Initial commit
0 parents  commit 327258e

17 files changed

+457
-0
lines changed

.dockerignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

.editorconfig

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Tells the .editorconfg plugin to stop searching once it finds this file
2+
root = true
3+
4+
[*]
5+
indent_size = 2
6+
indent_style = space
7+
charset = utf-8
8+
end_of_line = lf
9+
insert_final_newline = true
10+
trim_trailing_whitespace = true

.github/workflows/build-and-push.yml

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# This is a basic workflow to help you get started with Actions
2+
3+
name: Build and Push to Quay
4+
5+
on:
6+
push:
7+
branches: [ master ]
8+
9+
# Allows you to run this workflow manually from the Actions tab
10+
workflow_dispatch:
11+
12+
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
13+
jobs:
14+
# This workflow contains a single job called "build"
15+
build:
16+
# The type of runner that the job will run on
17+
runs-on: ubuntu-latest
18+
19+
# Steps represent a sequence of tasks that will be executed as part of the job
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v2
23+
24+
- name: Source to Image Build
25+
uses: redhat-actions/s2i-build@v2
26+
id: build_image
27+
with:
28+
builder_image: registry.access.redhat.com/ubi8/nodejs-12
29+
image: openshift-s2i-typescript-example
30+
# The tags of the image to build, separated by a space
31+
tags: latest ${{ github.sha }}
32+
# List of environment variable key-value pairs to pass to the s2i builder context
33+
env_vars: HUSKY_SKIP_INSTALL=1
34+
35+
- name: Push To Quay Action
36+
uses: redhat-actions/push-to-registry@v2
37+
with:
38+
image: ${{ steps.build_image.outputs.image }}
39+
tags: ${{ steps.build_image.outputs.tags }}
40+
registry: quay.io/evanshortiss
41+
username: ${{ secrets.QUAY_USERNAME }}
42+
password: ${{ secrets.QUAY_PASSWORD }}

.gitignore

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
node_modules
2+
package-lock.json
3+
.DS_Store
4+
5+
# These are generated - safe to ignore
6+
*.js
7+
*.js.map
8+
*.d.ts
9+
10+
# Nodeshift and other temp files
11+
tmp
12+
openshift.local.clusterup

.nvmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v12.16.1

Dockerfile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FROM node:12-alpine
2+
3+
WORKDIR /usr/app

README.md

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# TypeScript Sample Application
2+
3+
This repository provides a simple starting point for running TypeScript
4+
applications on OpenShift. It can also be applied to applications that use
5+
Babel or other transpilers.
6+
7+
A blogpost that runs through the details of this repository and OpenShift can
8+
be found [here](http://evanshortiss.com/development/openshift/javascript/typescript/2018/02/15/ts-on-openshift.html).
9+
10+
You can use this repository as a template, just click the green "Use this
11+
template" button at the top of this page on GitHub.
12+
13+
## Running on OpenShift via NodeShift
14+
To use this method of deployment you'll need:
15+
16+
* Node.js v12 or later
17+
* OpenShift 4.x (Run OpenShift 4.x locally using [CodeReady Containers](https://developers.redhat.com/products/codeready-containers/overview))
18+
19+
NodeShift is a neat CLI that simplifies deployment of Node.js applications on
20+
OpenShift. This project incldues NodeShift in `devDependencies`.
21+
22+
You can run the following to deploy it on an OpenShift instance:
23+
24+
```
25+
$ git clone [email protected]:evanshortiss/openshift-typescript-example.git ts-openshift
26+
27+
$ cd ts-openshift
28+
29+
# Ensure you are logged into your openshift instance
30+
$ oc login
31+
32+
# Choose the project you'd like to deploy this applicaion into
33+
$ oc new-project ts-example
34+
35+
# Build, deploy, and expose an endpoint for the service
36+
$ npm run nodeshift -- --expose
37+
```
38+
39+
If you're deploying on a locally running instance of OpenShift you might need
40+
to do the following to bypass the self-signed certificate issues:
41+
42+
```
43+
$ npm run nodeshift -- --expose --strictSSL=false
44+
```
45+
46+
## Run Locally without Docker
47+
To run this application locally you'll need:
48+
49+
* Node.js v12 or later
50+
* npm v6 or later
51+
* Git
52+
53+
Execute the following commands to start the program locally:
54+
55+
```
56+
git clone [email protected]:evanshortiss/openshift-typescript-example.git ts-openshift
57+
58+
cd ts-openshift
59+
60+
npm instal
61+
npm run build
62+
npm start
63+
```
64+
65+
If you're developing locally, start a live reload server like so:
66+
67+
```
68+
npm run start-dev
69+
```
70+
71+
## Build Locally using Source-to-Image (s2i)
72+
To perform the following steps you'll need:
73+
74+
* [Docker](https://docs.docker.com/release-notes/) (v19.x tested)
75+
* [s2i](https://github.com/openshift/source-to-image/releases) (v1.1.13 tested)
76+
77+
With both tools installed, execute the following commands to run your
78+
application locally. This will create a container that matches the one created
79+
using an OpenShift Build.
80+
81+
```bash
82+
# Run the s2i build script
83+
./scripts/s2i.sh
84+
85+
# Run the container image
86+
docker run -p 8080:8080 quay.io/evanshortiss/openshift-s2i-typescript-example
87+
```
88+
89+
## Running Local Dev Mode using Docker Compose
90+
To perform the following steps you'll need:
91+
92+
* [Docker](https://docs.docker.com/release-notes/) (v19.x tested)
93+
* [Docker Compose](https://docs.docker.com/compose/install/)
94+
95+
Run the `docker-compose up` command from the root of the repository to start
96+
Node.js and Redis containers.
97+
98+
The application will be available on port 8080.

docker-compose.yml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
version: '3'
2+
3+
services:
4+
redis:
5+
container_name: redis
6+
image: redis:6.2-alpine
7+
command: redis-server --requirepass redispass
8+
ts-node:
9+
depends_on:
10+
- redis
11+
container_name: ts-node
12+
build: .
13+
command: sh -c "npm i && npm run start-dev"
14+
ports:
15+
- 8080:8080/tcp
16+
environment:
17+
HUSKY_SKIP_INSTALL: 1
18+
REDIS_SERVICE_PASS: redispass
19+
REDIS_SERVICE_HOST: redis
20+
volumes:
21+
- '.:/usr/app/'
22+
restart: unless-stopped

package.json

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"name": "openshift-ts",
3+
"version": "4.0.0",
4+
"description": "A sample TypeScript application that can be deployed on OpenShift",
5+
"main": "src/server.js",
6+
"scripts": {
7+
"build": "tsc",
8+
"nodeshift": "nodeshift --expose",
9+
"postinstall": "npm run build",
10+
"format": "prettier --write --single-quote src/**/*.ts",
11+
"start": "node src/server.js",
12+
"start-dev": "nodemon --legacy-watch -e ts -x ts-node src/server.ts | pino-pretty -t",
13+
"test": "echo \"Error: no test specified\" && exit 1"
14+
},
15+
"husky": {
16+
"hooks": {
17+
"pre-commit": "npm run format && git add ."
18+
}
19+
},
20+
"repository": {
21+
"type": "git",
22+
"url": "git+https://github.com/evanshortiss/openshift-typescript-example.git"
23+
},
24+
"author": "Evan Shortiss <[email protected]> (https://evanshortiss.com/)",
25+
"license": "MIT",
26+
"bugs": {
27+
"url": "https://github.com/evanshortiss/openshift-typescript-example/issues"
28+
},
29+
"homepage": "https://github.com/evanshortiss/openshift-typescript-example#readme",
30+
"dependencies": {
31+
"env-var": "~6.3.0",
32+
"express": "~4.17.1",
33+
"express-handlebars": "^5.3.2",
34+
"helmet": "~4.1.0",
35+
"kube-probe": "~0.5.0",
36+
"morgan": "~1.10.0",
37+
"pino": "~6.5.1",
38+
"redis": "^3.1.2"
39+
},
40+
"devDependencies": {
41+
"@types/express": "~4.17.8",
42+
"@types/express-handlebars": "^3.1.0",
43+
"@types/node": "~12.12.54",
44+
"@types/pino": "~6.3.0",
45+
"@types/redis": "^2.8.28",
46+
"husky": "~4.2.5",
47+
"nodemon": "~2.0.7",
48+
"nodeshift": "^8.2.0",
49+
"pino-pretty": "~4.1.0",
50+
"prettier": "~2.1.1",
51+
"ts-node": "~9.1.1",
52+
"typescript": "~4.0.2"
53+
}
54+
}

scripts/s2i.sh

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
3+
IMAGE_REPOSITORY=${IMAGE_REPOSITORY:-quay.io/evanshortiss/openshift-s2i-typescript-example:latest}
4+
5+
# Don't want to copy in local node_modules to the build
6+
rm -rf node_modules/
7+
8+
# Build local codebase using source-to-image. Skip the husky hooks
9+
# installation using env vars. Use red hat ubi8 nodejs 12 as the base
10+
# image and tag using the given tag name
11+
s2i build -c . \
12+
-e HUSKY_SKIP_INSTALL=1 \
13+
registry.access.redhat.com/ubi8/nodejs-12 ${IMAGE_REPOSITORY}

src/config.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { from, logger } from 'env-var';
2+
import * as pino from 'pino';
3+
4+
const { get } = from(process.env, {}, logger);
5+
6+
const config = {
7+
PORT: get('PORT').default('8080').asPortNumber(),
8+
9+
LOG_LEVEL: get('LOG_LEVEL')
10+
.default('debug')
11+
.asEnum(Object.keys(pino.levels.values)),
12+
13+
REDIS_SERVICE_HOST: get('REDIS_SERVICE_HOST').asString(),
14+
REDIS_SERVICE_PORT: get('REDIS_SERVICE_PORT').default(6379).asPortNumber(),
15+
REDIS_SERVICE_PASS: get('REDIS_SERVICE_PASS').asString(),
16+
};
17+
18+
export = config;

src/log.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import pino from 'pino';
2+
import { LOG_LEVEL } from './config';
3+
4+
export default pino({
5+
level: LOG_LEVEL,
6+
});

src/redis.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import redis from 'redis';
2+
import {
3+
REDIS_SERVICE_HOST,
4+
REDIS_SERVICE_PORT,
5+
REDIS_SERVICE_PASS,
6+
} from './config';
7+
import log from './log';
8+
9+
let client!: redis.RedisClient;
10+
11+
if (REDIS_SERVICE_HOST) {
12+
log.info(
13+
`connecting redis client to: ${REDIS_SERVICE_HOST}:${REDIS_SERVICE_PORT}`
14+
);
15+
client = redis.createClient({
16+
host: REDIS_SERVICE_HOST,
17+
port: REDIS_SERVICE_PORT,
18+
password: REDIS_SERVICE_PASS,
19+
});
20+
}
21+
22+
export function getViewCount(): Promise<void | number> {
23+
return new Promise((resolve, reject) => {
24+
if (!client) {
25+
log.warn('no redis client. cannot return view count');
26+
resolve();
27+
} else {
28+
client.incr('views', (err, n) => {
29+
if (err) {
30+
reject(err);
31+
} else {
32+
resolve(n);
33+
}
34+
});
35+
}
36+
});
37+
}

src/server.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import express from 'express';
2+
import { PORT } from './config';
3+
import { getViewCount } from './redis';
4+
import log from './log';
5+
import exphbs from 'express-handlebars';
6+
7+
const app = express();
8+
const hbs = exphbs();
9+
10+
// Add kubernetes liveness and readiness probes at
11+
// /api/health/readiness and /api/health/liveness
12+
require('kube-probe')(app);
13+
14+
// Configure handlebars rendering
15+
app.engine('handlebars', hbs);
16+
app.set('view engine', 'handlebars');
17+
18+
// Include sensible security headers by default
19+
app.use(require('helmet')());
20+
// Log incoming requests
21+
app.use(require('morgan')('combined'));
22+
23+
// Respond with an index.html file for the default route
24+
app.get('/', async (req: express.Request, res: express.Response) => {
25+
// Include a view count header if Redis is connected
26+
const viewCount = await getViewCount();
27+
28+
res.render('index', { viewCount });
29+
});
30+
31+
// Our "Hello, World" endpoint. Can be passed a querystring "name" parameter
32+
app.get('/api/hello', (req: express.Request, res: express.Response) => {
33+
const name = req.query.name || 'World';
34+
const message = `Hello, ${name}!`;
35+
36+
log.debug(`returning message: "${message}"`);
37+
38+
res.json({
39+
message,
40+
});
41+
});
42+
43+
app.listen(PORT, () => {
44+
log.info(`🚀 server listening on port ${PORT}`);
45+
});

0 commit comments

Comments
 (0)