├── .gitignore
├── 2020-08-10-smaller-docker-containers
├── README.md
├── go
│ ├── Dockerfile.alpine
│ ├── Dockerfile.alpine-multi
│ ├── Dockerfile.naive
│ ├── Makefile
│ └── server.go
└── node
│ ├── Dockerfile.alpine
│ ├── Dockerfile.naive
│ ├── Dockerfile.slim
│ ├── Makefile
│ ├── package.json
│ └── server.js
├── 2020-08-17-more-secure-docker-containers
├── Dockerfile.distroless
├── Dockerfile.naive
├── Dockerfile.non-root
├── Makefile
├── README.md
├── package.json
└── server.js
├── 2020-08-31-docker-compose
├── .gitignore
├── Makefile
├── README.md
├── client
│ ├── .dockerignore
│ ├── .gitignore
│ ├── Dockerfile
│ ├── Makefile
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── src
│ │ ├── api
│ │ │ └── index.js
│ │ ├── app
│ │ │ └── index.js
│ │ ├── components
│ │ │ ├── Links.jsx
│ │ │ ├── Logo.jsx
│ │ │ ├── NavBar.jsx
│ │ │ └── index.js
│ │ ├── index.js
│ │ ├── logo.svg
│ │ ├── pages
│ │ │ ├── MoviesInsert.jsx
│ │ │ ├── MoviesList.jsx
│ │ │ ├── MoviesUpdate.jsx
│ │ │ └── index.js
│ │ ├── setupTests.js
│ │ └── style
│ │ │ └── index.js
│ └── yarn.lock
├── docker-compose.yml
└── server
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── Makefile
│ ├── controllers
│ └── movie-ctrl.js
│ ├── db
│ └── index.js
│ ├── index.js
│ ├── models
│ └── movie-model.js
│ ├── package.json
│ ├── routes
│ └── movie-router.js
│ └── yarn.lock
├── 2020-09-21-docker-compose-cos
├── Makefile
├── README.md
├── alias.sh
└── django-example
│ ├── Dockerfile
│ ├── README.md
│ ├── docker-compose.yml
│ └── requirements.txt
├── 2020-09-28-gcs-cloudflare-static-site
├── README.md
├── create-bucket.sh
└── my-website
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
│ ├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── serviceWorker.js
│ └── setupTests.js
│ └── yarn.lock
├── 2020-10-05-port-knocking
├── Dockerfile
├── Makefile
├── README.md
├── entrypoint.sh
├── index.html
├── knockd.conf
└── port_sequence.py
├── 2020-11-02-secret-management
├── 0-hardcode
│ └── db-index.js
├── 1-env-file
│ ├── db-index.js
│ └── secrets.env
├── 2-encrypted-env-file
│ ├── .gitignore
│ ├── Makefile
│ ├── db-index.js
│ └── secrets.env.enc
├── 3-dedicated-secret-manager
│ ├── Makefile
│ ├── db-index-direct.js
│ └── db-index-env.js
├── 4-ephemeral-credentials-with-vault
│ └── README.md
└── README.md
├── 2020-11-11-github-super-linter
├── .github
│ └── workflows
│ │ └── linter.yaml
├── .gitignore
├── Makefile
├── README.md
└── python
│ ├── guess_number_fixed.py
│ ├── guess_number_nolint.py
│ └── pyproject.toml
├── 2020-11-30-5k-milestone-celebration-generative-art
├── .gitignore
├── Makefile
├── README.md
├── __init__.py
├── channel_ids.json
├── channel_ids_manual.json
├── constants.py
├── convert_video.py
├── generate_images.py
├── poetry.lock
├── pyproject.toml
├── reshape_subscriber_data.py
├── style_transfer.py
├── subscriber-gallery
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── Album.js
│ │ ├── App.js
│ │ ├── artists.json
│ │ ├── index.js
│ │ └── theme.js
│ └── yarn.lock
├── yt_channels.py
├── yt_get_subscriber_data.py
├── yt_studio.py
└── yt_subscriber_api_queries_failed.py
├── 2020-12-27-productionize-mern
├── .gitignore
├── Makefile
├── README.md
├── client
│ ├── .dockerignore
│ ├── .gitignore
│ ├── Caddyfile.local
│ ├── Caddyfile.production
│ ├── Dockerfile.dev
│ ├── Dockerfile.production
│ ├── Makefile
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── src
│ │ ├── api
│ │ │ └── index.js
│ │ ├── app
│ │ │ └── index.js
│ │ ├── components
│ │ │ ├── Links.jsx
│ │ │ ├── Logo.jsx
│ │ │ ├── NavBar.jsx
│ │ │ └── index.js
│ │ ├── index.js
│ │ ├── logo.svg
│ │ ├── pages
│ │ │ ├── MoviesInsert.jsx
│ │ │ ├── MoviesList.jsx
│ │ │ ├── MoviesUpdate.jsx
│ │ │ └── index.js
│ │ ├── setupTests.js
│ │ └── style
│ │ │ └── index.js
│ └── yarn.lock
├── docker-compose-dev.yml
├── docker-compose-production.yml
├── sequence.md
└── server
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── Makefile
│ ├── config
│ └── dev.env
│ ├── controllers
│ └── movie-ctrl.js
│ ├── db
│ └── index.js
│ ├── index.js
│ ├── models
│ └── movie-model.js
│ ├── package.json
│ ├── routes
│ └── movie-router.js
│ └── yarn.lock
├── 2021-01-27-multi-arch-docker
├── Dockerfile
├── Makefile
├── README.md
├── package.json
└── server.js
├── 2021-04-03-smallest-possible-container
├── Makefile
├── README.md
├── asm
│ ├── .DS_Store
│ ├── .dockerignore
│ ├── .gitignore
│ ├── Dockerfile
│ ├── LICENSE
│ ├── Makefile
│ ├── README.md
│ ├── bss.asm
│ ├── constants.asm
│ ├── data.asm
│ ├── debug.asm
│ ├── http.asm
│ ├── macros.asm
│ ├── main.asm
│ ├── mutex.asm
│ ├── string.asm
│ ├── syscall.asm
│ └── web_root
│ │ └── index.html
├── go
│ ├── Dockerfile
│ ├── Dockerfile.alpine
│ ├── Dockerfile.alpine-multi
│ ├── Dockerfile.naive
│ ├── Dockerfile.scratch
│ ├── index.html
│ └── server.go
└── node
│ ├── Dockerfile
│ ├── Dockerfile.alpine
│ ├── Dockerfile.naive
│ ├── Dockerfile.slim
│ ├── index.html
│ └── index.js
├── 2021-04-19-10000-pods
├── Makefile
├── README.md
├── deployment.yaml
└── visualizations
│ ├── .gitignore
│ ├── Makefile
│ ├── plot.py
│ ├── poetry.lock
│ └── pyproject.toml
├── 2021-08-15-docker-dev-environment
├── .gitignore
├── README.md
├── improved
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── Makefile
│ ├── docker-compose.yml
│ ├── package.json
│ └── server.js
└── naive
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── Makefile
│ ├── package.json
│ └── server.js
├── 2022-08-01-getting-started-with-k8s
├── .gitignore
├── Dockerfile
├── README.md
├── app
│ ├── __init__.py
│ └── main.py
├── kubernetes
│ ├── deployment.yaml
│ ├── ingress.yaml
│ └── service.yaml
└── requirements.txt
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Video ideas that I never made... 😳
2 | 2020-12-XX-cloud-GPU
3 | 2021-07-XX-letsencrypt-docker-compose
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/README.md:
--------------------------------------------------------------------------------
1 | # How to make smaller Docker containers (including multi-stage build demo!)
2 |
3 | Video: https://youtu.be/2KzDMD5Qk2k
4 |
5 |
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/go/Dockerfile.alpine:
--------------------------------------------------------------------------------
1 | FROM golang:1.14-alpine
2 | WORKDIR /app
3 | COPY . ./
4 | RUN go build -o server .
5 | CMD ["/app/server"]
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/go/Dockerfile.alpine-multi:
--------------------------------------------------------------------------------
1 | ### First Stage ###
2 | FROM golang:1.14-alpine as build
3 | WORKDIR /app
4 | COPY . ./
5 | RUN go build -o server .
6 |
7 | ################
8 |
9 | ### Second Stage ###
10 | FROM alpine:3.12
11 | COPY --from=build /app /app
12 | WORKDIR /app
13 | EXPOSE 8081
14 | CMD ["/app/server"]
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/go/Dockerfile.naive:
--------------------------------------------------------------------------------
1 | FROM golang:1.14
2 | WORKDIR /app
3 | COPY . ./
4 | RUN go build -o server .
5 | CMD ["/app/server"]
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/go/Makefile:
--------------------------------------------------------------------------------
1 | run:
2 | docker run -i -d -p 8081:8081 go-naive
3 |
4 | build-naive:
5 | docker build \
6 | -f $(CURDIR)/Dockerfile.naive \
7 | -t go-naive \
8 | .
9 |
10 | build-alpine:
11 | docker build \
12 | -f $(CURDIR)/Dockerfile.alpine \
13 | -t go-alpine \
14 | .
15 |
16 | build-alpine-multi:
17 | docker build \
18 | -f $(CURDIR)/Dockerfile.alpine-multi \
19 | -t go-alpine-multi \
20 | .
21 |
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/go/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | )
7 |
8 | func main() {
9 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
10 | fmt.Fprintf(w, "Hello from golang!")
11 | })
12 | http.ListenAndServe(":8081", nil)
13 | }
14 |
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/node/Dockerfile.alpine:
--------------------------------------------------------------------------------
1 | FROM node:12-alpine
2 | WORKDIR /usr/src/app
3 |
4 | COPY package*.json ./
5 | RUN npm install
6 | COPY server.js ./
7 |
8 | CMD ["server.js" ]
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/node/Dockerfile.naive:
--------------------------------------------------------------------------------
1 | FROM node:12
2 | WORKDIR /usr/src/app
3 |
4 | COPY package*.json ./
5 | RUN npm install
6 | COPY server.js ./
7 |
8 | CMD ["server.js" ]
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/node/Dockerfile.slim:
--------------------------------------------------------------------------------
1 | FROM node:12-slim
2 | WORKDIR /usr/src/app
3 |
4 | COPY package*.json ./
5 | RUN npm install
6 | COPY server.js ./
7 |
8 | CMD ["server.js" ]
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/node/Makefile:
--------------------------------------------------------------------------------
1 | run:
2 | docker run -i -d -p 8080:8080 node-naive
3 |
4 | # 918MB
5 | build-naive:
6 | docker build \
7 | -f $(CURDIR)/Dockerfile.naive \
8 | -t node-naive \
9 | .
10 |
11 | # 142MB
12 | build-slim:
13 | docker build \
14 | -f $(CURDIR)/Dockerfile.slim \
15 | -t node-slim \
16 | .
17 |
18 | # 89.3MB
19 | build-alpine:
20 | docker build \
21 | -f $(CURDIR)/Dockerfile.alpine \
22 | -t node-alpine \
23 | .
24 |
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-world",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC"
11 | }
12 |
--------------------------------------------------------------------------------
/2020-08-10-smaller-docker-containers/node/server.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 |
3 | const hostname = '0.0.0.0';
4 | const port = 8080;
5 |
6 | const server = http.createServer((req, res) => {
7 | res.statusCode = 200;
8 | res.setHeader('Content-Type', 'text/plain');
9 | res.end('Hello from node.js!');
10 | });
11 |
12 | server.listen(port, hostname, () => {
13 | console.log(`Server running at http://${hostname}:${port}/`);
14 | });
--------------------------------------------------------------------------------
/2020-08-17-more-secure-docker-containers/Dockerfile.distroless:
--------------------------------------------------------------------------------
1 | ### First Stage ###
2 | # Base Image
3 | FROM node:12-slim as build
4 | WORKDIR /usr/src/app
5 |
6 | # Install Dependencies
7 | COPY package*.json ./
8 | RUN npm install
9 |
10 | # Copy in Application
11 | COPY . .
12 |
13 | ### Second Stage ###
14 | FROM gcr.io/distroless/nodejs:12
15 |
16 | # Copy App + Dependencies from Build Stage
17 | COPY --from=build /usr/src/app /usr/src/app
18 | WORKDIR /usr/src/app
19 |
20 | # Set User to Non-Root
21 | USER 1000
22 |
23 | # Run Server
24 | CMD [ "server.js" ]
--------------------------------------------------------------------------------
/2020-08-17-more-secure-docker-containers/Dockerfile.naive:
--------------------------------------------------------------------------------
1 | # Base Image
2 | FROM node:12-slim
3 | WORKDIR /usr/src/app
4 |
5 | # Install Dependencies
6 | COPY package*.json ./
7 | RUN npm install
8 |
9 | # Copy in Application
10 | COPY . .
11 |
12 | # Run Server
13 | CMD [ "server.js" ]
--------------------------------------------------------------------------------
/2020-08-17-more-secure-docker-containers/Dockerfile.non-root:
--------------------------------------------------------------------------------
1 | # Base Image
2 | FROM node:12-slim
3 | WORKDIR /usr/src/app
4 |
5 | # Install Dependencies
6 | COPY package*.json ./
7 | RUN npm install
8 |
9 | # Copy in Application
10 | COPY . .
11 |
12 | # Set User to Non-Root
13 | USER node
14 |
15 | # Run Server
16 | CMD [ "server.js" ]
--------------------------------------------------------------------------------
/2020-08-17-more-secure-docker-containers/Makefile:
--------------------------------------------------------------------------------
1 | run:
2 | docker run -i -d -p 8080:8080 node-distroless
3 |
4 | build-naive:
5 | docker build \
6 | -f $(CURDIR)/Dockerfile.naive \
7 | -t node-naive \
8 | .
9 |
10 | build-non-root:
11 | docker build \
12 | -f $(CURDIR)/Dockerfile.non-root \
13 | -t node-non-root \
14 | .
15 |
16 | build-distroless:
17 | docker build \
18 | -f $(CURDIR)/Dockerfile.distroless \
19 | -t node-distroless \
20 | .
--------------------------------------------------------------------------------
/2020-08-17-more-secure-docker-containers/README.md:
--------------------------------------------------------------------------------
1 | # How to secure your Docker containers! (5 practical tips with example Dockerfiles! 🐳)
2 |
3 | Video: https://youtu.be/JE2PJbbpjsM
4 |
5 |
--------------------------------------------------------------------------------
/2020-08-17-more-secure-docker-containers/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-world",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC"
11 | }
12 |
--------------------------------------------------------------------------------
/2020-08-17-more-secure-docker-containers/server.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 |
3 | const hostname = '0.0.0.0';
4 | const port = 8080;
5 |
6 | const server = http.createServer((req, res) => {
7 | res.statusCode = 200;
8 | res.setHeader('Content-Type', 'text/plain');
9 | res.end('Hello from node.js!');
10 | });
11 |
12 | server.listen(port, hostname, () => {
13 | console.log(`Server running at http://${hostname}:${port}/`);
14 | });
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | cd server && $(MAKE) build
3 | cd client && $(MAKE) build
4 |
5 | run:
6 | docker-compose up
7 |
8 | stop:
9 | docker-compose down
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/README.md:
--------------------------------------------------------------------------------
1 | # MERN Docker Compose Demo
2 |
3 | Video: https://youtu.be/0B2raYYH2fE
4 |
5 | I followed [this tutorial](https://medium.com/swlh/how-to-create-your-first-mern-mongodb-express-js-react-js-and-node-js-stack-7e8b20463e66) to get basic app working.
6 |
7 | I then containerized the api server and react client and created docker-compose to connect them.
8 |
9 | ---
10 |
11 | Run `make build` from root to build containers
12 | Run `make run` from root to run containers with docker-compose
13 |
14 | ---
15 |
16 | **NOTE:** This is a development configuration where the react app is being served by a separate container. We would also want to create a production version where we build a static version of the react site and serve it with something like nginx.
17 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14-slim
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY ./package.json ./
6 | COPY ./yarn.lock ./
7 |
8 | RUN yarn install
9 |
10 | COPY . .
11 |
12 | EXPOSE 3000
13 |
14 | CMD [ "yarn", "start" ]
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | docker build -t react-app .
3 |
4 | run:
5 | docker run -i -d -p 3000:3000 react-app
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `yarn start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `yarn test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `yarn build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `yarn eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `yarn build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "axios": "^0.19.2",
10 | "bootstrap": "^4.5.2",
11 | "react": "^16.13.1",
12 | "react-dom": "^16.13.1",
13 | "react-router-dom": "^5.2.0",
14 | "react-scripts": "3.4.3",
15 | "react-table": "^6",
16 | "styled-components": "^5.1.1"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": "react-app"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-08-31-docker-compose/client/public/favicon.ico
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-08-31-docker-compose/client/public/logo192.png
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-08-31-docker-compose/client/public/logo512.png
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/api/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | const api = axios.create({
4 | baseURL: 'http://localhost:5000/api',
5 | })
6 |
7 | export const insertMovie = payload => api.post(`/movie`, payload)
8 | export const getAllMovies = () => api.get(`/movies`)
9 | export const updateMovieById = (id, payload) => api.put(`/movie/${id}`, payload)
10 | export const deleteMovieById = id => api.delete(`/movie/${id}`)
11 | export const getMovieById = id => api.get(`/movie/${id}`)
12 |
13 | const apis = {
14 | insertMovie,
15 | getAllMovies,
16 | updateMovieById,
17 | deleteMovieById,
18 | getMovieById,
19 | }
20 |
21 | export default apis
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/app/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
3 |
4 | import { NavBar } from '../components'
5 | import { MoviesList, MoviesInsert, MoviesUpdate } from '../pages'
6 |
7 | import 'bootstrap/dist/css/bootstrap.min.css'
8 |
9 | function App() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 | )
24 | }
25 |
26 | export default App
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/components/Links.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import styled from 'styled-components'
4 |
5 | const Collapse = styled.div.attrs({
6 | className: 'collpase navbar-collapse',
7 | })``
8 |
9 | const List = styled.div.attrs({
10 | className: 'navbar-nav mr-auto',
11 | })``
12 |
13 | const Item = styled.div.attrs({
14 | className: 'collpase navbar-collapse',
15 | })``
16 |
17 | class Links extends Component {
18 | render() {
19 | return (
20 |
21 |
22 | Simple MERN Application
23 |
24 |
25 |
26 | -
27 |
28 | List Movies
29 |
30 |
31 | -
32 |
33 | Create Movie
34 |
35 |
36 |
37 |
38 |
39 | )
40 | }
41 | }
42 |
43 | export default Links
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/components/Logo.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styled from 'styled-components'
3 |
4 | import logo from '../logo.svg'
5 |
6 | const Wrapper = styled.a.attrs({
7 | className: 'navbar-brand',
8 | })``
9 |
10 | class Logo extends Component {
11 | render() {
12 | return (
13 |
14 |
15 |
16 | )
17 | }
18 | }
19 |
20 | export default Logo
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/components/NavBar.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styled from 'styled-components'
3 |
4 | import Logo from './Logo'
5 | import Links from './Links'
6 |
7 | const Container = styled.div.attrs({
8 | className: 'container',
9 | })``
10 |
11 | const Nav = styled.nav.attrs({
12 | className: 'navbar navbar-expand-lg navbar-dark bg-dark',
13 | })`
14 | margin-bottom: 20 px;
15 | `
16 |
17 | class NavBar extends Component {
18 | render() {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 | }
28 | }
29 |
30 | export default NavBar
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/components/index.js:
--------------------------------------------------------------------------------
1 |
2 | import Links from './Links'
3 | import Logo from './Logo'
4 | import NavBar from './NavBar'
5 |
6 | export { Links, Logo, NavBar }
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/index.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import ReactDOM from 'react-dom'
4 | import App from './app'
5 |
6 | ReactDOM.render( , document.getElementById('root'))
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/pages/MoviesInsert.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import api from '../api'
3 |
4 | import styled from 'styled-components'
5 |
6 | const Title = styled.h1.attrs({
7 | className: 'h1',
8 | })``
9 |
10 | const Wrapper = styled.div.attrs({
11 | className: 'form-group',
12 | })`
13 | margin: 0 30px;
14 | `
15 |
16 | const Label = styled.label`
17 | margin: 5px;
18 | `
19 |
20 | const InputText = styled.input.attrs({
21 | className: 'form-control',
22 | })`
23 | margin: 5px;
24 | `
25 |
26 | const Button = styled.button.attrs({
27 | className: `btn btn-primary`,
28 | })`
29 | margin: 15px 15px 15px 5px;
30 | `
31 |
32 | const CancelButton = styled.a.attrs({
33 | className: `btn btn-danger`,
34 | })`
35 | margin: 15px 15px 15px 5px;
36 | `
37 |
38 | class MoviesInsert extends Component {
39 | constructor(props) {
40 | super(props)
41 |
42 | this.state = {
43 | name: '',
44 | rating: '',
45 | time: '',
46 | }
47 | }
48 |
49 | handleChangeInputName = async event => {
50 | const name = event.target.value
51 | this.setState({ name })
52 | }
53 |
54 | handleChangeInputRating = async event => {
55 | const rating = event.target.validity.valid
56 | ? event.target.value
57 | : this.state.rating
58 |
59 | this.setState({ rating })
60 | }
61 |
62 | handleChangeInputTime = async event => {
63 | const time = event.target.value
64 | this.setState({ time })
65 | }
66 |
67 | handleIncludeMovie = async () => {
68 | const { name, rating, time } = this.state
69 | const arrayTime = time.split('/')
70 | const payload = { name, rating, time: arrayTime }
71 |
72 | await api.insertMovie(payload).then(res => {
73 | window.alert(`Movie inserted successfully`)
74 | this.setState({
75 | name: '',
76 | rating: '',
77 | time: '',
78 | })
79 | })
80 | }
81 |
82 | render() {
83 | const { name, rating, time } = this.state
84 | return (
85 |
86 | Create Movie
87 |
88 | Name:
89 |
94 |
95 | Rating:
96 |
106 |
107 | Time:
108 |
113 |
114 | Add Movie
115 | Cancel
116 |
117 | )
118 | }
119 | }
120 |
121 | export default MoviesInsert
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/pages/MoviesList.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactTable from 'react-table'
3 | import api from '../api'
4 |
5 | import styled from 'styled-components'
6 |
7 | import 'react-table/react-table.css'
8 |
9 | const Wrapper = styled.div`
10 | padding: 0 40px 40px 40px;
11 | `
12 |
13 | const Update = styled.div`
14 | color: #ef9b0f;
15 | cursor: pointer;
16 | `
17 |
18 | const Delete = styled.div`
19 | color: #ff0000;
20 | cursor: pointer;
21 | `
22 |
23 | class UpdateMovie extends Component {
24 | updateUser = event => {
25 | event.preventDefault()
26 |
27 | window.location.href = `/movies/update/${this.props.id}`
28 | }
29 |
30 | render() {
31 | return Update
32 | }
33 | }
34 |
35 | class DeleteMovie extends Component {
36 | deleteUser = event => {
37 | event.preventDefault()
38 |
39 | if (
40 | window.confirm(
41 | `Do tou want to delete the movie ${this.props.id} permanently?`,
42 | )
43 | ) {
44 | api.deleteMovieById(this.props.id)
45 | window.location.reload()
46 | }
47 | }
48 |
49 | render() {
50 | return Delete
51 | }
52 | }
53 |
54 | class MoviesList extends Component {
55 | constructor(props) {
56 | super(props)
57 | this.state = {
58 | movies: [],
59 | columns: [],
60 | isLoading: false,
61 | }
62 | }
63 |
64 | componentDidMount = async () => {
65 | this.setState({ isLoading: true })
66 |
67 | await api.getAllMovies().then(movies => {
68 | this.setState({
69 | movies: movies.data.data,
70 | isLoading: false,
71 | })
72 | })
73 | }
74 |
75 | render() {
76 | const { movies, isLoading } = this.state
77 | console.log('TCL: MoviesList -> render -> movies', movies)
78 |
79 | const columns = [
80 | {
81 | Header: 'ID',
82 | accessor: '_id',
83 | filterable: true,
84 | },
85 | {
86 | Header: 'Name',
87 | accessor: 'name',
88 | filterable: true,
89 | },
90 | {
91 | Header: 'Rating',
92 | accessor: 'rating',
93 | filterable: true,
94 | },
95 | {
96 | Header: 'Time',
97 | accessor: 'time',
98 | Cell: props => {props.value.join(' / ')} ,
99 | },
100 | {
101 | Header: '',
102 | accessor: '',
103 | Cell: function(props) {
104 | return (
105 |
106 |
107 |
108 | )
109 | },
110 | },
111 | {
112 | Header: '',
113 | accessor: '',
114 | Cell: function(props) {
115 | return (
116 |
117 |
118 |
119 | )
120 | },
121 | },
122 | ]
123 |
124 | let showTable = true
125 | if (!movies.length) {
126 | showTable = false
127 | }
128 |
129 | return (
130 |
131 | {showTable && (
132 |
140 | )}
141 |
142 | )
143 | }
144 | }
145 |
146 | export default MoviesList
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/pages/MoviesUpdate.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import api from '../api'
3 |
4 | import styled from 'styled-components'
5 |
6 | const Title = styled.h1.attrs({
7 | className: 'h1',
8 | })``
9 |
10 | const Wrapper = styled.div.attrs({
11 | className: 'form-group',
12 | })`
13 | margin: 0 30px;
14 | `
15 |
16 | const Label = styled.label`
17 | margin: 5px;
18 | `
19 |
20 | const InputText = styled.input.attrs({
21 | className: 'form-control',
22 | })`
23 | margin: 5px;
24 | `
25 |
26 | const Button = styled.button.attrs({
27 | className: `btn btn-primary`,
28 | })`
29 | margin: 15px 15px 15px 5px;
30 | `
31 |
32 | const CancelButton = styled.a.attrs({
33 | className: `btn btn-danger`,
34 | })`
35 | margin: 15px 15px 15px 5px;
36 | `
37 |
38 | class MoviesUpdate extends Component {
39 | constructor(props) {
40 | super(props)
41 |
42 | this.state = {
43 | id: this.props.match.params.id,
44 | name: '',
45 | rating: '',
46 | time: '',
47 | }
48 | }
49 |
50 | handleChangeInputName = async event => {
51 | const name = event.target.value
52 | this.setState({ name })
53 | }
54 |
55 | handleChangeInputRating = async event => {
56 | const rating = event.target.validity.valid
57 | ? event.target.value
58 | : this.state.rating
59 |
60 | this.setState({ rating })
61 | }
62 |
63 | handleChangeInputTime = async event => {
64 | const time = event.target.value
65 | this.setState({ time })
66 | }
67 |
68 | handleUpdateMovie = async () => {
69 | const { id, name, rating, time } = this.state
70 | const arrayTime = time.split('/')
71 | const payload = { name, rating, time: arrayTime }
72 |
73 | await api.updateMovieById(id, payload).then(res => {
74 | window.alert(`Movie updated successfully`)
75 | this.setState({
76 | name: '',
77 | rating: '',
78 | time: '',
79 | })
80 | })
81 | }
82 |
83 | componentDidMount = async () => {
84 | const { id } = this.state
85 | const movie = await api.getMovieById(id)
86 |
87 | this.setState({
88 | name: movie.data.data.name,
89 | rating: movie.data.data.rating,
90 | time: movie.data.data.time.join('/'),
91 | })
92 | }
93 |
94 | render() {
95 | const { name, rating, time } = this.state
96 | return (
97 |
98 | Create Movie
99 |
100 | Name:
101 |
106 |
107 | Rating:
108 |
118 |
119 | Time:
120 |
125 |
126 | Update Movie
127 | Cancel
128 |
129 | )
130 | }
131 | }
132 |
133 | export default MoviesUpdate
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import MoviesList from './MoviesList'
2 | import MoviesInsert from './MoviesInsert'
3 | import MoviesUpdate from './MoviesUpdate'
4 |
5 | export { MoviesList, MoviesInsert, MoviesUpdate }
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/client/src/style/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-08-31-docker-compose/client/src/style/index.js
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | react-app:
4 | image: react-app
5 | stdin_open: true
6 | ports:
7 | - "3000:3000"
8 | networks:
9 | - mern-app
10 | api-server:
11 | image: api-server
12 | ports:
13 | - "5000:5000"
14 | networks:
15 | - mern-app
16 | depends_on:
17 | - mongo
18 | mongo:
19 | image: mongo:3.6.19-xenial
20 | ports:
21 | - "27017:27017"
22 | networks:
23 | - mern-app
24 | volumes:
25 | - mongo-data:/data/db
26 | networks:
27 | mern-app:
28 | driver: bridge
29 | volumes:
30 | mongo-data:
31 | driver: local
32 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/server/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14-slim
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY ./package.json ./
6 | COPY ./yarn.lock ./
7 |
8 | RUN yarn install
9 |
10 | COPY . .
11 |
12 | EXPOSE 5000
13 |
14 | CMD [ "index.js" ]
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/server/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | docker build -t api-server .
3 |
4 | run:
5 | docker run -d -p 5000:5000 api-server
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/server/controllers/movie-ctrl.js:
--------------------------------------------------------------------------------
1 | const Movie = require('../models/movie-model')
2 |
3 | createMovie = (req, res) => {
4 | const body = req.body
5 |
6 | if (!body) {
7 | return res.status(400).json({
8 | success: false,
9 | error: 'You must provide a movie',
10 | })
11 | }
12 |
13 | const movie = new Movie(body)
14 |
15 | if (!movie) {
16 | return res.status(400).json({ success: false, error: err })
17 | }
18 |
19 | movie
20 | .save()
21 | .then(() => {
22 | return res.status(201).json({
23 | success: true,
24 | id: movie._id,
25 | message: 'Movie created!',
26 | })
27 | })
28 | .catch(error => {
29 | return res.status(400).json({
30 | error,
31 | message: 'Movie not created!',
32 | })
33 | })
34 | }
35 |
36 | updateMovie = async (req, res) => {
37 | const body = req.body
38 |
39 | if (!body) {
40 | return res.status(400).json({
41 | success: false,
42 | error: 'You must provide a body to update',
43 | })
44 | }
45 |
46 | Movie.findOne({ _id: req.params.id }, (err, movie) => {
47 | if (err) {
48 | return res.status(404).json({
49 | err,
50 | message: 'Movie not found!',
51 | })
52 | }
53 | movie.name = body.name
54 | movie.time = body.time
55 | movie.rating = body.rating
56 | movie
57 | .save()
58 | .then(() => {
59 | return res.status(200).json({
60 | success: true,
61 | id: movie._id,
62 | message: 'Movie updated!',
63 | })
64 | })
65 | .catch(error => {
66 | return res.status(404).json({
67 | error,
68 | message: 'Movie not updated!',
69 | })
70 | })
71 | })
72 | }
73 |
74 | deleteMovie = async (req, res) => {
75 | await Movie.findOneAndDelete({ _id: req.params.id }, (err, movie) => {
76 | if (err) {
77 | return res.status(400).json({ success: false, error: err })
78 | }
79 |
80 | if (!movie) {
81 | return res
82 | .status(404)
83 | .json({ success: false, error: `Movie not found` })
84 | }
85 |
86 | return res.status(200).json({ success: true, data: movie })
87 | }).catch(err => console.log(err))
88 | }
89 |
90 | getMovieById = async (req, res) => {
91 | await Movie.findOne({ _id: req.params.id }, (err, movie) => {
92 | if (err) {
93 | return res.status(400).json({ success: false, error: err })
94 | }
95 |
96 | if (!movie) {
97 | return res
98 | .status(404)
99 | .json({ success: false, error: `Movie not found` })
100 | }
101 | return res.status(200).json({ success: true, data: movie })
102 | }).catch(err => console.log(err))
103 | }
104 |
105 | getMovies = async (req, res) => {
106 | await Movie.find({}, (err, movies) => {
107 | if (err) {
108 | return res.status(400).json({ success: false, error: err })
109 | }
110 | if (!movies.length) {
111 | return res
112 | .status(404)
113 | .json({ success: false, error: `Movie not found` })
114 | }
115 | return res.status(200).json({ success: true, data: movies })
116 | }).catch(err => console.log(err))
117 | }
118 |
119 | module.exports = {
120 | createMovie,
121 | updateMovie,
122 | deleteMovie,
123 | getMovies,
124 | getMovieById,
125 | }
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/server/db/index.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const connectionString = 'mongodb://mongo:27017/cinema';
4 |
5 | mongoose.connect(connectionString, { useNewUrlParser: true }).catch((e) => {
6 | console.error('Connection error', e.message);
7 | });
8 |
9 | const db = mongoose.connection;
10 |
11 | module.exports = db;
12 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/server/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const bodyParser = require('body-parser')
3 | const cors = require('cors')
4 |
5 | const db = require('./db')
6 | const movieRouter = require('./routes/movie-router')
7 |
8 | const app = express()
9 | const apiPort = 5000
10 |
11 | app.use(bodyParser.urlencoded({ extended: true }))
12 | app.use(cors())
13 | app.use(bodyParser.json())
14 |
15 | db.on('error', console.error.bind(console, 'MongoDB connection error:'))
16 |
17 | app.get('/', (req, res) => {
18 | res.send('Hello World!')
19 | })
20 |
21 | app.use('/api', movieRouter)
22 |
23 | app.listen(apiPort, () => console.log(`Server running on port ${apiPort}`))
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/server/models/movie-model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 | const Schema = mongoose.Schema
3 |
4 | const Movie = new Schema(
5 | {
6 | name: { type: String, required: true },
7 | time: { type: [String], required: true },
8 | rating: { type: Number, required: true },
9 | },
10 | { timestamps: true },
11 | )
12 |
13 | module.exports = mongoose.model('movies', Movie)
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "dependencies": {
7 | "body-parser": "^1.19.0",
8 | "cors": "^2.8.5",
9 | "express": "^4.17.1",
10 | "mongoose": "^5.9.29",
11 | "nodemon": "^2.0.4"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/2020-08-31-docker-compose/server/routes/movie-router.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 |
3 | const MovieCtrl = require('../controllers/movie-ctrl')
4 |
5 | const router = express.Router()
6 |
7 | router.post('/movie', MovieCtrl.createMovie)
8 | router.put('/movie/:id', MovieCtrl.updateMovie)
9 | router.delete('/movie/:id', MovieCtrl.deleteMovie)
10 | router.get('/movie/:id', MovieCtrl.getMovieById)
11 | router.get('/movies', MovieCtrl.getMovies)
12 |
13 | module.exports = router
--------------------------------------------------------------------------------
/2020-09-21-docker-compose-cos/Makefile:
--------------------------------------------------------------------------------
1 | PROJECT_ID:=devops-directive-project
2 | REGION:=us-central1
3 | ZONE:=us-central1-a
4 | VM_NAME:=docker-compose
5 | USER:=palas
6 | SSH_STRING:=$(USER)@$(VM_NAME)
7 |
8 | HOME_PATH:=/home/$(USER)
9 |
10 | create-static-ip:
11 | gcloud compute \
12 | addresses create $(VM_NAME) \
13 | --project=$(PROJECT_ID) \
14 | --region=$(REGION)
15 |
16 | delete-static-ip:
17 | gcloud compute \
18 | addresses delete $(VM_NAME) \
19 | --project=$(PROJECT_ID) \
20 | --region=$(REGION) \
21 |
22 | create-vm:
23 | gcloud beta compute \
24 | instances create $(VM_NAME) \
25 | --project=$(PROJECT_ID) \
26 | --zone=$(ZONE) \
27 | --address=$(VM_NAME) \
28 | --machine-type=e2-medium \
29 | --subnet=default \
30 | --network-tier=PREMIUM \
31 | --metadata=google-logging-enabled=true \
32 | --maintenance-policy=MIGRATE \
33 | --service-account=1006868470482-compute@developer.gserviceaccount.com \
34 | --scopes=https://www.googleapis.com/auth/devstorage.read_only,https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/service.management.readonly,https://www.googleapis.com/auth/trace.append \
35 | --tags=http-server \
36 | --image=cos-stable-81-12871-1196-0 \
37 | --image-project=cos-cloud \
38 | --boot-disk-size=10GB \
39 | --boot-disk-type=pd-standard \
40 | --boot-disk-device-name=$(VM_NAME)
41 |
42 | delete-vm:
43 | gcloud compute \
44 | --project=$(PROJECT_ID) \
45 | instances delete $(VM_NAME) \
46 | --zone=$(ZONE) \
47 |
48 | #################
49 |
50 | ssh:
51 | gcloud compute ssh $(SSH_STRING) --project=$(PROJECT_ID) --zone=$(ZONE)
52 |
53 | define send-ssh-command
54 | gcloud compute ssh $(SSH_STRING) \
55 | --project=$(PROJECT_ID) \
56 | --zone=$(ZONE) \
57 | --command $(1)
58 | endef
59 |
60 | CONFIGURE_DOCKER_AUTH:="docker-credential-gcr configure-docker"
61 | configure-gcr:
62 | $(call send-ssh-command,$(CONFIGURE_DOCKER_AUTH))
63 |
64 | REPO:=https://github.com/sidpalas/devops-directive.git
65 | CLONE_REPO:="docker run -i --rm -v $(HOME_PATH):/root -v $(HOME_PATH):/git alpine/git clone $(REPO)"
66 | clone-repo:
67 | $(call send-ssh-command,$(CLONE_REPO))
68 |
69 | define ALIAS_COMMAND
70 | "`cat ./alias.sh`"
71 | endef
72 |
73 | alias-docker-compose:
74 | $(call send-ssh-command,$(ALIAS_COMMAND))
75 |
--------------------------------------------------------------------------------
/2020-09-21-docker-compose-cos/README.md:
--------------------------------------------------------------------------------
1 | # Using docker-compose on Google's Container Optimized OS (COS)
2 |
3 | Video: https://youtu.be/GoOB8YoRSbA
4 |
5 | ## Resources
6 |
7 | https://cloud.google.com/community/tutorials/docker-compose-on-container-optimized-os
8 |
9 | docker compose w/ GCR... https://github.com/fpgaminer/docker-compose-gcr/blob/master/Dockerfile
10 |
11 | Potential security issues with mounting docker socket: https://blog.secureideas.com/2018/05/escaping-the-whale-things-you-probably-shouldnt-do-with-docker-part-1.html
12 |
--------------------------------------------------------------------------------
/2020-09-21-docker-compose-cos/alias.sh:
--------------------------------------------------------------------------------
1 | echo alias docker-compose="'"'docker run --rm \
2 | -v /var/run/docker.sock:/var/run/docker.sock \
3 | -v "$PWD:$PWD" \
4 | -w="$PWD" \
5 | cryptopants/docker-compose-gcr@sha256:a59f0d61e424e52d3fed72a151241bfe6f4f4611f2e4f5e928ef4eeb47662a54'"'" >> ~/.bashrc; \
6 | source ~/.bashrc;
--------------------------------------------------------------------------------
/2020-09-21-docker-compose-cos/django-example/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3-slim
2 | ENV PYTHONUNBUFFERED 1
3 | RUN mkdir /code
4 | WORKDIR /code
5 | COPY requirements.txt /code/
6 | RUN pip install -r requirements.txt
7 | COPY . /code/
8 |
--------------------------------------------------------------------------------
/2020-09-21-docker-compose-cos/django-example/README.md:
--------------------------------------------------------------------------------
1 | # Docker-compose on container optimized OS
2 |
3 | ## Customizing Makefile
4 |
5 | Replace `PROJECT_ID` and `USER` to match your GCP project id and username
6 |
7 | ## Creating Static IP address and VM
8 |
9 | `make create-static-ip && make create-vm`
10 |
11 | ## Deleting when done (so you dont keep getting charged)
12 |
13 | `make delete-vm` --> `y`
14 | `make delete-static-ip` -> `y`
15 |
16 | ## SSH onto VM:
17 |
18 | `make ssh`
19 |
20 | ---
21 |
22 | ## Django docker-compose example
23 |
24 | ### link
25 |
26 | https://docs.docker.com/compose/django/
27 |
--------------------------------------------------------------------------------
/2020-09-21-docker-compose-cos/django-example/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | db:
5 | image: postgres
6 | environment:
7 | - POSTGRES_DB=postgres
8 | - POSTGRES_USER=postgres
9 | - POSTGRES_PASSWORD=postgres
10 | web:
11 | image: gcr.io/devops-directive-project:django
12 | command: python manage.py runserver 0.0.0.0:8000
13 | volumes:
14 | - .:/code
15 | ports:
16 | - "80:8000"
17 | depends_on:
18 | - db
--------------------------------------------------------------------------------
/2020-09-21-docker-compose-cos/django-example/requirements.txt:
--------------------------------------------------------------------------------
1 | Django>=3.0,<4.0
2 | psycopg2-binary>=2.8
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/README.md:
--------------------------------------------------------------------------------
1 | # Using Google Cloud Storage + Cloudflare to Host a Static Website for just PENNIES PER MONTH!
2 |
3 | Video: https://youtu.be/sUr4GBzEqNs
4 |
5 | ---
6 |
7 | Setup Steps:
8 |
9 | 1. Purchase domain
10 | 2. Add Cloudflare nameservers
11 | 3. Add CNAME record
12 | 4. Set SSL/TLS to Flexible
13 | 5. Confirm domain ownership
14 | 6. Create bucket
15 | 7. Configure website settings
16 | 8. Modify permissions
17 | 9. Add files
18 |
19 | `create-bucket.sh` handles steps 6-9!
20 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/create-bucket.sh:
--------------------------------------------------------------------------------
1 | # set environment variables
2 | export PROJECT_ID=
3 | export DOMAIN=
4 |
5 | # create the bucket
6 | gsutil mb -p $PROJECT_ID -b on gs://$DOMAIN
7 |
8 | # set website config
9 | gsutil web set -m index.html -e 404.html gs://$DOMAIN
10 |
11 | # add user permissions
12 | gsutil iam ch allUsers:legacyObjectReader gs://$DOMAIN
13 |
14 | # copy the website files!
15 | gsutil -m rsync -d -r my-website/build gs://$DOMAIN
16 |
17 | # 🎉🎉🎉
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `yarn start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `yarn test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `yarn build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `yarn eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `yarn build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-world",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "react": "^16.13.1",
10 | "react-dom": "^16.13.1",
11 | "react-scripts": "3.4.3"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test",
17 | "eject": "react-scripts eject"
18 | },
19 | "eslintConfig": {
20 | "extends": "react-app"
21 | },
22 | "browserslist": {
23 | "production": [
24 | ">0.2%",
25 | "not dead",
26 | "not op_mini all"
27 | ],
28 | "development": [
29 | "last 1 chrome version",
30 | "last 1 firefox version",
31 | "last 1 safari version"
32 | ]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-09-28-gcs-cloudflare-static-site/my-website/public/favicon.ico
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-09-28-gcs-cloudflare-static-site/my-website/public/logo192.png
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-09-28-gcs-cloudflare-static-site/my-website/public/logo512.png
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import logo from './logo.svg';
3 | import './App.css';
4 |
5 | function App() {
6 | return (
7 |
22 | );
23 | }
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render( );
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://bit.ly/CRA-PWA
17 | serviceWorker.unregister();
18 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/2020-09-28-gcs-cloudflare-static-site/my-website/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/2020-10-05-port-knocking/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.8-alpine
2 |
3 | RUN apk add --no-cache \
4 | iptables \
5 | knock
6 |
7 | COPY ./entrypoint.sh ./
8 | RUN chmod +x ./entrypoint.sh
9 |
10 | COPY knockd.conf /etc/knockd.conf
11 |
12 | COPY ./index.html ./
13 |
14 | CMD [ "./entrypoint.sh" ]
15 |
--------------------------------------------------------------------------------
/2020-10-05-port-knocking/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | docker build -t port-knock-demo .
3 |
4 | run:
5 | docker run -d \
6 | --rm --privileged \
7 | -p 8000:8000 \
8 | -p 7000:7000 \
9 | -p 9000:9000 \
10 | -p 8888:8888 \
11 | --name port-knock-demo \
12 | port-knock-demo
13 |
14 | unlock:
15 | -telnet 127.0.0.1 7000
16 | -telnet 127.0.0.1 8000
17 | -telnet 127.0.0.1 9000
18 |
19 | lock:
20 | -telnet 127.0.0.1 9000
21 | -telnet 127.0.0.1 8000
22 | -telnet 127.0.0.1 7000
23 |
24 | test:
25 | curl -m 1 127.0.0.1:8888
26 |
27 | view-logs:
28 | docker exec port-knock-demo cat /var/log/knockd.log
--------------------------------------------------------------------------------
/2020-10-05-port-knocking/README.md:
--------------------------------------------------------------------------------
1 | # Port Knocking (Network Security Technique) Explained and Demoed in 5 Minutes!
2 |
3 | Video: https://youtu.be/IBR3oLqGBj4
4 |
5 | ## Test setup
6 |
7 | A container running:
8 |
9 | 1) Python `http.server` on port 8888
10 | 2) [knock](https://linux.die.net/man/1/knockd)
11 |
12 | ## To see it in action
13 |
14 | ```
15 | make build
16 | make run
17 |
18 | make test # curl will timeout
19 |
20 | make unlock
21 |
22 | make test # Hello from the webserver!
23 |
24 | make lock
25 |
26 | make test # curl will timeout
27 | ```
28 |
29 | More commonly, knockd would be configured to unlock a port for a specified period of time before automatically closing it again.
30 |
31 |
--------------------------------------------------------------------------------
/2020-10-05-port-knocking/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # NOTE: Docker opens up port 8888 because I am port forwarding
3 | # to it in my docker run command so I have to reclose it here
4 | /sbin/iptables -A INPUT -p tcp --dport 8888 -j DROP & \
5 | knockd & \
6 | python -m http.server 8888
7 |
--------------------------------------------------------------------------------
/2020-10-05-port-knocking/index.html:
--------------------------------------------------------------------------------
1 | Hello from the webserver!
2 |
--------------------------------------------------------------------------------
/2020-10-05-port-knocking/knockd.conf:
--------------------------------------------------------------------------------
1 | [options]
2 | logfile = /var/log/knockd.log
3 |
4 |
5 | [openHTTP]
6 | sequence = 7000,8000,9000
7 | seq_timeout = 5
8 | command = /sbin/iptables -D INPUT -p tcp --dport 8888 -j DROP
9 | tcpflags = syn
10 |
11 | [closeHTTP]
12 | sequence = 9000,8000,7000
13 | seq_timeout = 5
14 | command = /sbin/iptables -A INPUT -p tcp --dport 8888 -j DROP
15 | tcpflags = syn
16 |
--------------------------------------------------------------------------------
/2020-10-05-port-knocking/port_sequence.py:
--------------------------------------------------------------------------------
1 | def gen_port_sequence(
2 | current_timestamp,
3 | source_ip_address,
4 | other_things
5 | ) -> list(int):
6 | port_sequence = []
7 | # Fancy logic to generate
8 | # sequence of ports here...
9 | return port_sequence
10 |
--------------------------------------------------------------------------------
/2020-11-02-secret-management/0-hardcode/db-index.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const connectionString =
4 | 'mongodb://myUser:superSecretPassword@localhost:27017/databaseName';
5 |
6 | mongoose.connect(connectionString, { useNewUrlParser: true }).catch((e) => {
7 | console.error('Connection error', e.message);
8 | });
9 |
10 | const db = mongoose.connection;
11 |
12 | module.exports = db;
13 |
--------------------------------------------------------------------------------
/2020-11-02-secret-management/1-env-file/db-index.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('dotenv')
2 | const mongoose = require('mongoose');
3 |
4 | dotenv.config({ path: './secrets.env' })
5 |
6 | const connectionString =
7 | `mongodb://myUser:${process.env.DB_PASS}@localhost:27017/myDatabaseName`;
8 |
9 | mongoose.connect(connectionString, { useNewUrlParser: true }).catch((e) => {
10 | console.error('Connection error', e.message);
11 | });
12 |
13 | const db = mongoose.connection;
14 |
15 | module.exports = db;
16 |
--------------------------------------------------------------------------------
/2020-11-02-secret-management/1-env-file/secrets.env:
--------------------------------------------------------------------------------
1 | DB_PASS=superSecretPassword
2 |
--------------------------------------------------------------------------------
/2020-11-02-secret-management/2-encrypted-env-file/.gitignore:
--------------------------------------------------------------------------------
1 | secrets.env
--------------------------------------------------------------------------------
/2020-11-02-secret-management/2-encrypted-env-file/Makefile:
--------------------------------------------------------------------------------
1 | ENCRYPTION_KEY=Where-am-I-supposed-to-store-this?!
2 |
3 | encrypt-secrets-file:
4 | openssl aes-256-cbc -a -salt -in secrets.env -out secrets.env.enc -pass pass:$(ENCRYPTION_KEY)
5 |
6 | decrypt-secrets-file:
7 | openssl aes-256-cbc -d -a -salt -in secrets.env.enc -out secrets.env -pass pass:$(ENCRYPTION_KEY)
8 |
--------------------------------------------------------------------------------
/2020-11-02-secret-management/2-encrypted-env-file/db-index.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('../3-dedicated-secret-manager/node_modules/dotenv')
2 | const mongoose = require('mongoose');
3 |
4 | // Load config
5 | dotenv.config({ path: './secrets.env.tmp' })
6 |
7 | const connectionString = `mongodb://myUsername:${process.env.DB_PASS}@localhost:27017/myDatabaseName`;
8 |
9 | mongoose.connect(connectionString, { useNewUrlParser: true }).catch((e) => {
10 | console.error('Connection error', e.message);
11 | });
12 |
13 | const db = mongoose.connection;
14 |
15 | module.exports = db;
16 |
--------------------------------------------------------------------------------
/2020-11-02-secret-management/2-encrypted-env-file/secrets.env.enc:
--------------------------------------------------------------------------------
1 | U2FsdGVkX18scNlIZ3kN7eL/NRW4+vmQBoW0eqM/GcFYUO+sJ7hVbf7dpTNllW7v
2 |
--------------------------------------------------------------------------------
/2020-11-02-secret-management/3-dedicated-secret-manager/Makefile:
--------------------------------------------------------------------------------
1 | PROJECT_ID:=
2 | SECRET_NAME:=DB_PASS
3 |
4 | define get-secret
5 | $(shell gcloud secrets versions access latest --secret=$(1) --project=$(PROJECT_ID))
6 | endef
7 |
8 | run-app:
9 | @DB_PASS=$(call get-secret,$(DB_PASS))" npm start
--------------------------------------------------------------------------------
/2020-11-02-secret-management/3-dedicated-secret-manager/db-index-direct.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | // Retrieve password from secret manager
4 | const {SecretManagerServiceClient} = require('@google-cloud/secret-manager');
5 | const name = 'projects//secrets/DB_PASS/versions/latest'
6 | const client = new SecretManagerServiceClient();
7 | const [version] = await client.accessSecretVersion({
8 | name: name,
9 | });
10 | const DB_PASS = version.payload.data.toString();
11 |
12 | const connectionString = `mongodb://myUsername:${DB_PASS}@localhost:27017/myDatabaseName`;
13 |
14 | mongoose.connect(connectionString, { useNewUrlParser: true }).catch((e) => {
15 | console.error('Connection error', e.message);
16 | });
17 |
18 | const db = mongoose.connection;
19 |
20 | module.exports = db;
21 |
--------------------------------------------------------------------------------
/2020-11-02-secret-management/3-dedicated-secret-manager/db-index-env.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('../3-dedicated-secret-manager/node_modules/dotenv')
2 | const mongoose = require('mongoose');
3 |
4 | const connectionString = `mongodb://myUsername:${process.env.DB_PASS}@localhost:27017/myDatabaseName`;
5 |
6 | mongoose.connect(connectionString, { useNewUrlParser: true }).catch((e) => {
7 | console.error('Connection error', e.message);
8 | });
9 |
10 | const db = mongoose.connection;
11 |
12 | module.exports = db;
13 |
--------------------------------------------------------------------------------
/2020-11-02-secret-management/4-ephemeral-credentials-with-vault/README.md:
--------------------------------------------------------------------------------
1 | Great example here: https://github.com/BenchLabs/talk-vault-ephemeral-credentials
2 |
--------------------------------------------------------------------------------
/2020-11-02-secret-management/README.md:
--------------------------------------------------------------------------------
1 | # How to Properly Manage Application Secrets (5 LEVELS)
2 |
3 | Video: https://youtu.be/7NTFZoDpzbQ
4 |
5 | ## Level -2 (uᴉɐɹq) - no authentication
6 |
7 | ## Level -1 (anti-brain) - make all your passwords "password" (or "maga2020!")
8 |
9 | ## Level 0 (no brain) - hard code wherever they are used
10 |
11 | ## Level 1 (small brain) - pull out into a config file (or .env file)
12 |
13 | ## Level 2 (medium brain) - encrypt .env file before checking in
14 |
15 | ## Level 3 (big brain) - move secrets to a tool designed to store secrets (GCP/AWS secret manager)
16 |
17 | ## Level 4 (cosmic brain) - ephemeral credentials w/ something like vault
--------------------------------------------------------------------------------
/2020-11-11-github-super-linter/.github/workflows/linter.yaml:
--------------------------------------------------------------------------------
1 | # NOTE: This would need to be in the root of the repo to use it
2 | # I placed it here because it is associated with this video
3 |
4 | name: Lint Code Base
5 |
6 | on:
7 | push:
8 | branches-ignore: [master]
9 | # Remove the line above to run when pushing to master
10 | pull_request:
11 | branches: [master]
12 |
13 | jobs:
14 | build:
15 | name: Lint Code Base
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - name: Checkout Code
20 | uses: actions/checkout@v2
21 | with:
22 | # Full git history is needed to get a proper list of changed files within `super-linter`
23 | fetch-depth: 0
24 |
25 | - name: Lint Code Base
26 | uses: github/super-linter@v3
27 | env:
28 | VALIDATE_ALL_CODEBASE: false
29 | DEFAULT_BRANCH: master
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/2020-11-11-github-super-linter/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 |
--------------------------------------------------------------------------------
/2020-11-11-github-super-linter/Makefile:
--------------------------------------------------------------------------------
1 | run-local:
2 | docker run \
3 | -e RUN_LOCAL=true \
4 | -v $(CURDIR):/tmp/lint \
5 | github/super-linter
6 |
--------------------------------------------------------------------------------
/2020-11-11-github-super-linter/README.md:
--------------------------------------------------------------------------------
1 | # GitHub Super Linter in 5 minutes! (Run Local + GitHub Actions)
2 |
3 | Video: https://youtu.be/fL8de_m8imY
--------------------------------------------------------------------------------
/2020-11-11-github-super-linter/python/guess_number_fixed.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | guesses_made = 0
4 | name = input("Hello! What is your name?\n")
5 |
6 | number = random.randint(1, 20)
7 | print(f"Well, {name}, I am thinking of a number between 1 and 20.")
8 |
9 | while guesses_made < 6:
10 | guess = int(input("Take a guess: "))
11 | guesses_made += 1
12 |
13 | if guess < number:
14 | print("Your guess is too low.")
15 |
16 | if guess > number:
17 | print("Your guess is too high.")
18 |
19 | if guess == number:
20 | break
21 |
22 | if guess == number:
23 | print(
24 | f"Good job, {name}! You guessed my number in {guesses_made} guesses! But this line is way too long!"
25 | )
26 | else:
27 | print("Nope. The number I was thinking of was {number}")
28 |
--------------------------------------------------------------------------------
/2020-11-11-github-super-linter/python/guess_number_nolint.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | guesses_made = 0
4 | name = input("Hello! What is your name?\n")
5 |
6 | number = random.randint(1, 20)
7 | print(f"Well, {name}, I am thinking of a number between 1 and 20.")
8 |
9 | while guesses_made < 6:
10 | guess = int(input("Take a guess: "))
11 | guesses_made += 1
12 |
13 | if guess < number:
14 | print("Your guess is too low.")
15 |
16 | if guess > number:
17 | print("Your guess is too high.")
18 |
19 | if guess == number:
20 | break
21 |
22 | if guess == number:
23 | print(f"Good job, {name}! You guessed my number in {guesses_made} guesses! But this line is way too long!")
24 | else:
25 | print(f"Nope. The number I was thinking of was {number}")
--------------------------------------------------------------------------------
/2020-11-11-github-super-linter/python/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | exclude = '''
3 | /(
4 | \.eggs
5 | | \.git
6 | | \.hg
7 | | \.mypy_cache
8 | | \.tox
9 | | \.venv
10 | | _build
11 | | buck-out
12 | | build
13 | | dist
14 | )/
15 | '''
16 | include = '\.pyi?$'
17 | line-length = 88
18 | target-version = ['py36', 'py37', 'py38']
19 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/.gitignore:
--------------------------------------------------------------------------------
1 | *secret.json
2 | photos/
3 | .DS_Store
4 | *.pyc
5 | magenta_arbitrary-image-stylization-v1-256_2.tar.gz
6 |
7 | internal-api-curl
8 |
9 | subscriber_data_array.json
10 | subscriber_data.json
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/Makefile:
--------------------------------------------------------------------------------
1 | PROJECT_ID:=devops-directive-project
2 | DOMAIN:=style-transfer.devopsdirective.com
3 |
4 | generate-images:
5 | poetry run python yt_get_subscriber_data.py
6 | poetry run python generate_images.py
7 | poetry run python reshape_subscriber_data.py
8 | cp ./subscriber_data_array.json ./subscriber-gallery/src/subscriber_data_array.json
9 | cp -r ./photos/* ./subscriber-gallery/public/photos/
10 |
11 | set-up-bucket:
12 | gsutil mb -p $(PROJECT_ID) -b on gs://$(DOMAIN)
13 | gsutil web set -m index.html -e 404.html gs://$(DOMAIN)
14 | gsutil iam ch allUsers:legacyObjectReader gs://$(DOMAIN)
15 |
16 | build-site:
17 | cd subscriber-gallery && yarn build
18 |
19 | deploy-website:
20 | gsutil -m rsync -r -c -d subscriber-gallery/build gs://$(DOMAIN)
21 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/README.md:
--------------------------------------------------------------------------------
1 | # 5000 Sub Celebration
2 |
3 | Video: https://youtu.be/XGcKdsgMrIY
4 |
5 | ---
6 |
7 | Uses TensorFlow Neural Style transfer model to stylize the profile images of DevOps Directive Subscribers!
8 |
9 | https://style-transfer.devopsdirective.com/
10 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-11-30-5k-milestone-celebration-generative-art/__init__.py
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/channel_ids_manual.json:
--------------------------------------------------------------------------------
1 | [
2 | "UC4MdpjzjPuop_qWNAvR23JA",
3 | "UCDHJAWoXqsN6ZuFfSuJknoA",
4 | "UC1cSiiIAx7Kni1ivz7-3rpQ",
5 | "UC1DHIqwiWtbdrSq8o8Ybc3Q",
6 | "UCvI5azOD4eDumpshr00EfIw",
7 | "UC8ha68gfkmh5v2a2BAx7low",
8 | "UCvtSjHhrpTDdUK0gkgQBBrg",
9 | "UC34UXFLKqdW3cpk5CBu2Siw",
10 | "UC29ju8bIPH5as8OGnQzwJyA",
11 | "UCl85NtF7zABq4mMK8n3DVXA",
12 | "UCd641cwgyAxe10fPxbERnqA",
13 | "UCNLK0w7HwllxvSrMduuboEA",
14 | "UCDCHcqyeQgJ-jVSd6VJkbCw",
15 | "UCmx1KZv0AImSC_umE98yxUA"
16 | ]
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/constants.py:
--------------------------------------------------------------------------------
1 | STYLE_IMAGES = {
2 | 'kandinsky.jpg': 'https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg',
3 | 'van-gogh.jpg': 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/2560px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg',
4 | 'mondrian.jpg': 'https://www.iamexpat.nl/sites/default/files/styles/article--full/public/mondrian-painting.jpg?itok=cDpeCUbY',
5 | 'da-vinci.jpg': 'https://www.sciencefriday.com/wp-content/uploads/2017/10/Fig.-114_fetus-min.jpg',
6 | 'munch.jpg': 'https://i.pinimg.com/564x/b6/f4/a0/b6f4a01ddc7d82eff528915c2247207d.jpg',
7 | 'picasso.jpg': 'https://i.pinimg.com/474x/8b/13/56/8b1356ddb41dc38cc2c21275eedb874b.jpg'
8 | }
9 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/convert_video.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import os
3 |
4 | from constants import STYLE_IMAGES
5 | from style_transfer import plain_image, stylize_image
6 |
7 | # ### extract frames
8 |
9 | video_capture = cv2.VideoCapture('video/test.mp4')
10 | success,image = video_capture.read()
11 | count = 0
12 | while success:
13 | cv2.imwrite(f"video/frames/frame{count}.jpg", image) # save frame as JPEG file
14 | success,image = video_capture.read()
15 | print('Read a new frame: ', success)
16 | count += 1
17 |
18 | ## stylize frames
19 | count = 0
20 | style_name = 'picasso.jpg'
21 | style_url = 'https://i.pinimg.com/474x/8b/13/56/8b1356ddb41dc38cc2c21275eedb874b.jpg'
22 | while True:
23 | try:
24 | frame_url = f"http://localhost:8000/frames/frame{count}.jpg"
25 | frame_name = f"picasso-frame{count}.jpg"
26 | stylized_path = f"video/stylized_frames/frame{count}.jpg"
27 | stylized_image = stylize_image(frame_name, frame_url, style_name, style_url)
28 | stylized_image.save(stylized_path)
29 | count += 1
30 | print(count)
31 | except:
32 | break
33 |
34 |
35 |
36 | ### reconstruct video
37 | path = "video/stylized_frames/frame0.jpg"
38 | frame = cv2.imread(path)
39 | fps = 30
40 | height,width,_ = frame.shape
41 | video_out = cv2.VideoWriter('video/picasso-out.mp4',cv2.VideoWriter_fourcc(*"mp4v"),fps,(width,height))
42 |
43 | count = 0
44 | frame_exists = True
45 | while frame_exists:
46 | frame = cv2.imread(path)
47 | video_out.write(frame)
48 | print("frame written")
49 |
50 | count += 1
51 | print(count)
52 | path = f"video/stylized_frames/frame{count}.jpg"
53 | frame_exists = os.path.exists(path)
54 |
55 |
56 | video_out.release()
57 | cv2.destroyAllWindows()
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/generate_images.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 |
4 | from constants import STYLE_IMAGES
5 | from style_transfer import plain_image, stylize_image
6 |
7 | def main():
8 | with open('./subscriber_data.json') as f:
9 | subscriber_data = json.load(f)
10 |
11 | count = 0
12 | for channel_id, subscriber in subscriber_data.items():
13 | sub_name = ''.join(e for e in subscriber.get("title") if e.isalnum())
14 | sub_url = subscriber.get("thumbnail_url")
15 | print(count, channel_id, sub_name, sub_url)
16 | print('\n')
17 | channel_path = os.path.join(os.getcwd(), "photos", channel_id)
18 | os.makedirs(channel_path, exist_ok=True)
19 |
20 | image_path = os.path.join(channel_path, "unstylized.jpg")
21 | unstylized_image = plain_image(sub_name, sub_url)
22 | # unstylized_image.show()
23 | unstylized_image.save(image_path)
24 |
25 | for style_name, style_url in STYLE_IMAGES.items():
26 | image_path = os.path.join(channel_path,style_name)
27 | if not os.path.exists(image_path):
28 | stylized_image = stylize_image(sub_name, sub_url, style_name, style_url)
29 | # stylized_image.show()
30 | stylized_image.save(image_path)
31 |
32 | count += 1
33 |
34 |
35 |
36 |
37 | if __name__ == "__main__":
38 | main()
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "2020-11-16-2500-celebration-generative-art"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["sid palas "]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | tensorflow = "^2.3.1"
10 | matplotlib = "^3.3.3"
11 | tensorflow_hub = "^0.10.0"
12 | google_auth_oauthlib = "^0.4.2"
13 | opencv-python = "^4.4.0"
14 |
15 | [tool.poetry.dev-dependencies]
16 | google-api-python-client = "^1.12.5"
17 | google-auth-oauthlib = "^0.4.2"
18 | google-auth-httplib2 = "^0.0.4"
19 | black = "^20.8b1"
20 |
21 | [build-system]
22 | requires = ["poetry>=0.12"]
23 | build-backend = "poetry.masonry.api"
24 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/reshape_subscriber_data.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | def main():
4 | with open("subscriber_data.json") as f:
5 | subscriber_data = json.load(f)
6 |
7 | subscriber_data_array = []
8 |
9 | for channel_id, subscriber in subscriber_data.items():
10 | subscriber_data_array.append({
11 | "channelId": channel_id,
12 | "title": subscriber['title']
13 | })
14 |
15 | with open("subscriber_data_array.json", "w") as f:
16 | json.dump(subscriber_data_array, f)
17 |
18 | if __name__ == "__main__":
19 | main()
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/style_transfer.py:
--------------------------------------------------------------------------------
1 | # https://www.tensorflow.org/tutorials/generative/style_transfer
2 |
3 | import os
4 | import matplotlib as mpl
5 | import matplotlib.pyplot as plt
6 | import numpy as np
7 | import PIL.Image
8 | import tensorflow as tf
9 | import tensorflow_hub as hub
10 |
11 | mpl.rcParams['figure.figsize'] = (12,12)
12 | mpl.rcParams['axes.grid'] = False
13 |
14 | # Load compressed models from tensorflow_hub
15 | os.environ['TFHUB_MODEL_LOAD_FORMAT'] = 'COMPRESSED'
16 |
17 |
18 | # hub_model = hub.load('https://tfhub.dev/google/magenta/arbitrary-image-stylization-v1-256/2')
19 |
20 | # Use local version of model file (remote version not working for some reason...)
21 | print('make sure to initialize local webserver!')
22 | hub_model = hub.load('http://localhost:8000/magenta_arbitrary-image-stylization-v1-256_2.tar.gz')
23 |
24 |
25 | def tensor_to_image(tensor):
26 | tensor = tensor*255
27 | tensor = np.array(tensor, dtype=np.uint8)
28 | if np.ndim(tensor)>3:
29 | assert tensor.shape[0] == 1
30 | tensor = tensor[0]
31 | return PIL.Image.fromarray(tensor)
32 |
33 |
34 | def load_img(path_to_img):
35 | max_dim = 512 # 1280
36 | img = tf.io.read_file(path_to_img)
37 | img = tf.image.decode_image(img, channels=3)
38 | img = tf.image.convert_image_dtype(img, tf.float32)
39 |
40 | shape = tf.cast(tf.shape(img)[:-1], tf.float32)
41 | long_dim = max(shape)
42 | scale = max_dim / long_dim
43 |
44 | new_shape = tf.cast(shape * scale, tf.int32)
45 |
46 | img = tf.image.resize(img, new_shape)
47 | img = img[tf.newaxis, :]
48 | return img
49 |
50 | def plain_image(image_name, image_url):
51 | content_path = tf.keras.utils.get_file(image_name, image_url)
52 | content_image = load_img(content_path)
53 | return tensor_to_image(content_image)
54 |
55 | def stylize_image(content_image_name, content_image_url, style_image_name, style_image_url):
56 | content_path = tf.keras.utils.get_file(content_image_name, content_image_url)
57 | style_path = tf.keras.utils.get_file(style_image_name, style_image_url)
58 |
59 | content_image = load_img(content_path)
60 | style_image = load_img(style_path)
61 |
62 | stylized_image_tensor = hub_model(tf.constant(content_image), tf.constant(style_image))[0]
63 | return tensor_to_image(stylized_image_tensor)
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/subscriber-gallery/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /public
14 | /photos
15 |
16 | # IDEs and editors
17 | /.idea
18 | /.vscode
19 |
20 | # misc
21 | .DS_Store
22 | .env.local
23 | .env.development.local
24 | .env.test.local
25 | .env.production.local
26 |
27 | npm-debug.log*
28 | yarn-debug.log*
29 | yarn-error.log*
30 |
31 | .eslintcache
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/subscriber-gallery/README.md:
--------------------------------------------------------------------------------
1 | # Create React App example
2 |
3 | ## How to use
4 |
5 | Download the example [or clone the repo](https://github.com/mui-org/material-ui):
6 |
7 | ```sh
8 | curl https://codeload.github.com/mui-org/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/create-react-app
9 | cd create-react-app
10 | ```
11 |
12 | Install it and run:
13 |
14 | ```sh
15 | npm install
16 | npm start
17 | ```
18 |
19 | or:
20 |
21 | [](https://codesandbox.io/s/github/mui-org/material-ui/tree/master/examples/create-react-app)
22 |
23 | ## The idea behind the example
24 |
25 | This example demonstrates how you can use [Create React App](https://github.com/facebookincubator/create-react-app).
26 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/subscriber-gallery/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-react-app",
3 | "version": "4.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "react-scripts start",
7 | "build": "react-scripts build",
8 | "test": "react-scripts test",
9 | "eject": "react-scripts eject"
10 | },
11 | "dependencies": {
12 | "@material-ui/core": "latest",
13 | "clsx": "latest",
14 | "fuse.js": "^6.4.3",
15 | "react": "latest",
16 | "react-dom": "latest",
17 | "react-scripts": "latest"
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/subscriber-gallery/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Container from '@material-ui/core/Container';
3 | import Typography from '@material-ui/core/Typography';
4 | import Album from './Album.js';
5 | import Box from '@material-ui/core/Box';
6 | import Link from '@material-ui/core/Link';
7 |
8 | function Copyright() {
9 | return (
10 |
11 |
12 | {'Copyright © '}
13 |
14 | DevOps Directive
15 | {' '}
16 | {new Date().getFullYear()}
17 | {'.'}
18 |
19 |
20 | );
21 | }
22 |
23 | export default function App() {
24 | return (
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/subscriber-gallery/src/artists.json:
--------------------------------------------------------------------------------
1 | ["kandinsky.jpg",
2 | "van-gogh.jpg",
3 | "da-vinci.jpg",
4 | "munch.jpg",
5 | "picasso.jpg",
6 | "mondrian.jpg"
7 | ]
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/subscriber-gallery/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import CssBaseline from '@material-ui/core/CssBaseline';
4 | import { ThemeProvider } from '@material-ui/core/styles';
5 | import App from './App';
6 | import theme from './theme';
7 |
8 | ReactDOM.render(
9 |
10 | {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
11 |
12 |
13 | ,
14 | document.querySelector('#root'),
15 | );
16 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/subscriber-gallery/src/theme.js:
--------------------------------------------------------------------------------
1 | import { red } from '@material-ui/core/colors';
2 | import { createMuiTheme } from '@material-ui/core/styles';
3 |
4 | // A custom theme for this app
5 | const theme = createMuiTheme({
6 | palette: {
7 | primary: {
8 | main: '#556cd6',
9 | },
10 | secondary: {
11 | main: '#19857b',
12 | },
13 | error: {
14 | main: red.A400,
15 | },
16 | background: {
17 | default: '#fff',
18 | },
19 | },
20 | });
21 |
22 | export default theme;
23 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/yt_channels.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import google_auth_oauthlib.flow
4 | import googleapiclient.discovery
5 | import googleapiclient.errors
6 |
7 |
8 | scopes = ["https://www.googleapis.com/auth/youtube.readonly"]
9 |
10 | os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
11 |
12 | api_service_name = "youtube"
13 | api_version = "v3"
14 | client_secrets_file = "google_oauth_client_secret.json"
15 |
16 | # Get credentials and create an API client
17 | flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(
18 | client_secrets_file, scopes
19 | )
20 | credentials = flow.run_console()
21 | youtube = googleapiclient.discovery.build(
22 | api_service_name, api_version, credentials=credentials
23 | )
24 |
25 |
26 | def get_channel_title_and_thumbnail_url(channel_id):
27 | request = youtube.channels().list(part="snippet", id=channel_id)
28 | response = request.execute()
29 | snippet = response["items"][0]["snippet"]
30 | title = snippet["title"]
31 | thumbnail_url = snippet["thumbnails"]["high"]["url"]
32 | return title, thumbnail_url
33 |
34 |
35 | def main():
36 | channel_ids = ["UC4MdpjzjPuop_qWNAvR23JA"]
37 | for channel_id in channel_ids:
38 | title, thumbnail_url = get_channel_title_and_thumbnail_url(
39 | channel_id
40 | )
41 | print(title, thumbnail_url)
42 |
43 | if __name__ == "__main__":
44 | main()
45 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/yt_get_subscriber_data.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | # from yt_channels import get_channel_title_and_thumbnail_url
4 | from yt_studio import get_subscriber_channel_ids
5 |
6 | def main(use_cache = True):
7 | if use_cache:
8 | with open("channel_ids.json") as f:
9 | channel_ids = json.load(f)
10 | else:
11 | channel_ids = get_subscriber_channel_ids()
12 |
13 |
14 | # Add custom list
15 | with open("channel_ids_manual.json") as f:
16 | channel_ids_manual = json.load(f)
17 |
18 | channel_ids.extend(channel_ids_manual)
19 |
20 | if use_cache:
21 | with open("subscriber_data.json") as f:
22 | subscriber_data = json.load(f)
23 | else:
24 | subscriber_data = {}
25 |
26 | count = 0
27 | for channel_id in channel_ids:
28 | count += 1
29 | if subscriber_data.get(channel_id):
30 | print(f'{channel_id} subscriber data already exists')
31 | continue
32 | title, thumbnail_url = get_channel_title_and_thumbnail_url(channel_id)
33 | print(count, channel_id, title, thumbnail_url)
34 | subscriber_data[channel_id] = {
35 | "title": title,
36 | "thumbnail_url": thumbnail_url
37 | }
38 |
39 |
40 | with open("subscriber_data.json", "w") as f:
41 | json.dump(subscriber_data, f)
42 |
43 |
44 | if __name__ == "__main__":
45 | main()
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/yt_studio.py:
--------------------------------------------------------------------------------
1 | import json
2 | import requests
3 |
4 |
5 | SUBS_PER_QUERY = 1000
6 |
7 |
8 | def get_subscribers(auth_hash, cookie_string, page_token):
9 | headers = {
10 | "authorization": f"SAPISIDHASH {auth_hash}",
11 | "content-type": "application/json",
12 | "origin": "https://studio.youtube.com",
13 | "referer": "https://studio.youtube.com/channel/UC4MdpjzjPuop_qWNAvR23JA",
14 | "cookie": cookie_string,
15 | }
16 |
17 | params = (
18 | ("alt", "json"),
19 | ("key", "AIzaSyBUPetSUmoZL-OhlxA7wSac5XinrygCqMo")
20 | )
21 |
22 | data = (
23 | '{"query":{"externalChannelId":"UC4MdpjzjPuop_qWNAvR23JA","order":"SUBSCRIBER_ORDER_SUBSCRIBER_COUNT_DESC","subscribedAfter":{"seconds":"0"},"numSubscriptionsRequested":"'
24 | + str(SUBS_PER_QUERY)
25 | + '"},"pageToken":"'
26 | + page_token
27 | + '","mask":{"channelId":true,"thumbnailDetails":{"all":true},"title":true,"metric":{"all":true},"permissions":{"all":true}},"context":{"client":{"clientName":62,"clientVersion":"1.20201118.04.00","hl":"en","gl":"US","experimentsToken":"","utcOffsetMinutes":-300},"request":{"returnLogEntry":true,"internalExperimentFlags":[{"key":"force_route_delete_playlist_to_outertube","value":"false"},{"key":"force_live_chat_merchandise_upsell","value":"false"}]},"user":{"onBehalfOfUser":"103601053026800248569","delegationContext":{"roleType":{"channelRoleType":"CREATOR_CHANNEL_ROLE_TYPE_OWNER"},"externalChannelId":"UC4MdpjzjPuop_qWNAvR23JA"},"serializedDelegationContext":"EhhVQzRNZHBqempQdW9wX3FXTkF2UjIzSkEqAggI"},"clientScreenNonce":"MC4zODQ3NDA5OTk2NzA1NzU5Ng.."}}'
28 | )
29 |
30 | response = requests.post(
31 | "https://studio.youtube.com/youtubei/v1/creator/list_creator_public_subscribers",
32 | headers=headers,
33 | params=params,
34 | data=data,
35 | )
36 |
37 | return response.json()
38 |
39 |
40 | def get_subscriber_channel_ids():
41 | print("Get auth_hash and cookie from network request in YT studio!")
42 | auth_hash = ""
43 | cookie_string = ""
44 | next_page_token = ""
45 |
46 | count = 0
47 | channel_ids = []
48 |
49 | while next_page_token != None:
50 | subscriber_api_response = get_subscribers(
51 | auth_hash, cookie_string, next_page_token
52 | )
53 |
54 | next_page_token = subscriber_api_response.get("nextPageToken")
55 | subscribers = subscriber_api_response.get("creatorChannelData")
56 | for subscriber in subscribers:
57 | channel_ids.append(subscriber["channelId"])
58 |
59 | print(next_page_token, count)
60 | count += SUBS_PER_QUERY
61 |
62 |
63 | with open("channel_ids.json", "w") as f:
64 | json.dump(channel_ids, f)
65 |
66 | return channel_ids
67 |
68 |
69 | if __name__ == "__main__":
70 | get_subscriber_channel_ids()
71 |
--------------------------------------------------------------------------------
/2020-11-30-5k-milestone-celebration-generative-art/yt_subscriber_api_queries_failed.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Sample Python code for youtube.subscriptions.list
4 | # See instructions for running these code samples locally:
5 | # https://developers.google.com/explorer-help/guides/code_samples#python
6 |
7 | import json
8 | import os
9 |
10 | import google_auth_oauthlib.flow
11 | import googleapiclient.discovery
12 | import googleapiclient.errors
13 |
14 | scopes = ["https://www.googleapis.com/auth/youtube.readonly"]
15 |
16 | def main():
17 | # Disable OAuthlib's HTTPS verification when running locally.
18 | # *DO NOT* leave this option enabled in production.
19 | os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
20 |
21 | api_service_name = "youtube"
22 | api_version = "v3"
23 | client_secrets_file = "google_oauth_client_secret.json"
24 |
25 | # Get credentials and create an API client
26 | flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(
27 | client_secrets_file, scopes)
28 | credentials = flow.run_console()
29 | youtube = googleapiclient.discovery.build(
30 | api_service_name, api_version, credentials=credentials)
31 |
32 | next_page_token = ''
33 | subscriber_dict = {}
34 | count = 0
35 |
36 | while next_page_token != None:
37 | print("Getting next page:", next_page_token, count)
38 |
39 | request = youtube.subscriptions().list(
40 | part="subscriberSnippet",
41 | maxResults=1,
42 | mySubscribers=True,
43 | pageToken=next_page_token
44 | )
45 | response = request.execute()
46 | next_page_token = response.get('nextPageToken')
47 |
48 | subscribers = response.get('items')
49 | for subscriber in subscribers:
50 | count += 1
51 | snippet = subscriber.get('subscriberSnippet')
52 | channel_id = snippet.get('channelId')
53 | title = snippet.get('title')
54 | url = snippet['thumbnails']['high']['url']
55 | subscriber_dict[channel_id] = {
56 | 'title': title,
57 | 'url': url
58 | }
59 |
60 | print('Total subscribers:', count)
61 |
62 | with open('subscribers.json', 'w') as f:
63 | json.dump(subscriber_dict, f)
64 |
65 |
66 | if __name__ == "__main__":
67 | main()
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 |
3 | /server/config/*
4 | !/server/config/dev.env
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/Makefile:
--------------------------------------------------------------------------------
1 | ### DEV
2 |
3 | build-dev:
4 | cd client && $(MAKE) build-dev
5 | cd server && $(MAKE) build
6 |
7 | run-dev:
8 | docker-compose -f docker-compose-dev.yml up
9 |
10 | ### LOCAL (prod config)
11 |
12 | build-local:
13 | cd client && $(MAKE) build-local
14 | cd server && $(MAKE) build
15 |
16 | run-local:
17 | ENV=local docker-compose -f docker-compose-production.yml up
18 |
19 |
20 | ### PROD
21 |
22 | build-production:
23 | cd client && $(MAKE) build-production
24 | cd server && $(MAKE) build
25 |
26 | run-production:
27 | ENV=production docker-compose -f docker-compose-production.yml up
28 |
29 | stop:
30 | docker-compose down
31 |
32 |
33 | ### REMOTE
34 |
35 | SSH_STRING:=root@161.35.104.130
36 |
37 | ssh:
38 | ssh $(SSH_STRING)
39 |
40 |
41 | # apt install make
42 |
43 | copy-files:
44 | scp -r ./* $(SSH_STRING):/root/
45 |
46 | # when you add firewall rule, have to add SSH on port 22 or it will stop working
47 |
48 | # run challenge with cloudflare on flexible, then bump to full
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/README.md:
--------------------------------------------------------------------------------
1 | # Deploy MERN Application with Docker Compose
2 |
3 | Live stream: https://youtu.be/DftsReyhz2Q
4 |
5 | A few months ago I released a videos showing how to run a MERN stack application with Docker Compose: [../2020-08-31-docker-compose](../2020-08-31-docker-compose).
6 |
7 | The configuration shown there is great for development but not ready for production. There are a number of steps we need to take to get it ready to deploy.
8 |
9 | - ✅ Mount code into dev version to enable hot reloading (make sure not to mount in node_modules)
10 | - ✅ Restart:unless stopped
11 | - ✅ Remove volume mounts for code in production
12 | - ✅ Environment specific urls (ENV vars for base url + mongo URI)
13 | - ✅ Add authentication for DB
14 | - ✅ Move DB to MongoDB Atlas
15 | - ✅ Build production version of front end react app
16 | - ✅ Serve static front end files from file server container
17 | - ✅ Set up SSL (using Caddy)
18 | - ✅ Create Digital Ocean VM
19 | - ✅ Create HTTP/HTTPS/SSH firewall rule and attach to VM
20 | - ✅ Whitelist IP address of Server in Atlas
21 | - TODO: Add authentication to the API (Separate Video)
22 |
23 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/Caddyfile.local:
--------------------------------------------------------------------------------
1 | http://localhost:80 {
2 | root * /srv
3 | route {
4 | reverse_proxy /api* api-server:5000
5 | try_files {path} {path}/ /index.html
6 | file_server
7 | }
8 | }
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/Caddyfile.production:
--------------------------------------------------------------------------------
1 | mern.mysuperawesomesite.com:443 {
2 | tls sid.palas@gmail.com
3 | root * /srv
4 | route {
5 | reverse_proxy /api* api-server:5000
6 | try_files {path} {path}/ /index.html
7 | file_server
8 | }
9 | }
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:14-slim
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY ./package.json ./
6 | COPY ./yarn.lock ./
7 |
8 | RUN yarn install
9 |
10 | COPY . .
11 |
12 | ENV REACT_APP_BASE_URL=http://localhost:5000/api
13 |
14 | EXPOSE 3000
15 |
16 | CMD [ "yarn", "start" ]
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/Dockerfile.production:
--------------------------------------------------------------------------------
1 | ### First Stage ###
2 | FROM node:14-slim AS builder
3 |
4 | WORKDIR /usr/src/app
5 |
6 | COPY ./package.json ./
7 | COPY ./yarn.lock ./
8 |
9 | RUN yarn install
10 |
11 | COPY . .
12 |
13 | ARG BASE_URL
14 | ENV REACT_APP_BASE_URL=${BASE_URL}
15 |
16 | RUN yarn build
17 |
18 | ### Second Stage ###
19 | FROM caddy:2.1.1
20 |
21 | ARG CADDYFILE
22 | COPY ${CADDYFILE} /etc/caddy/Caddyfile
23 |
24 | COPY --from=builder /usr/src/app/build/ /srv
25 |
26 | EXPOSE 80
27 |
28 | EXPOSE 443
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/Makefile:
--------------------------------------------------------------------------------
1 | build-dev:
2 | docker build -t react-app-dev -f Dockerfile.dev .
3 |
4 | ###################
5 |
6 | build-local:
7 | docker build \
8 | -t react-app-production:local \
9 | --build-arg CADDYFILE=Caddyfile.local \
10 | --build-arg BASE_URL=http://localhost:5000/api \
11 | -f Dockerfile.production .
12 |
13 | ###################
14 |
15 | build-production:
16 | docker build \
17 | -t react-app-production:production \
18 | --build-arg CADDYFILE=Caddyfile.production \
19 | --build-arg BASE_URL=https://mern.mysuperawesomesite.com/api \
20 | -f Dockerfile.production .
21 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `yarn start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `yarn test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `yarn build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `yarn eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `yarn build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "axios": "^0.19.2",
10 | "bootstrap": "^4.5.2",
11 | "react": "^16.13.1",
12 | "react-dom": "^16.13.1",
13 | "react-router-dom": "^5.2.0",
14 | "react-scripts": "3.4.3",
15 | "react-table": "^6",
16 | "styled-components": "^5.1.1"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": "react-app"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-12-27-productionize-mern/client/public/favicon.ico
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-12-27-productionize-mern/client/public/logo192.png
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-12-27-productionize-mern/client/public/logo512.png
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/api/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | const baseURL = process.env.REACT_APP_BASE_URL
4 |
5 | const api = axios.create({
6 | baseURL
7 | })
8 |
9 | export const insertMovie = payload => api.post(`/movie`, payload)
10 | export const getAllMovies = () => api.get(`/movies`)
11 | export const updateMovieById = (id, payload) => api.put(`/movie/${id}`, payload)
12 | export const deleteMovieById = id => api.delete(`/movie/${id}`)
13 | export const getMovieById = id => api.get(`/movie/${id}`)
14 |
15 | const apis = {
16 | insertMovie,
17 | getAllMovies,
18 | updateMovieById,
19 | deleteMovieById,
20 | getMovieById,
21 | }
22 |
23 | export default apis
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/app/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
3 |
4 | import { NavBar } from '../components'
5 | import { MoviesList, MoviesInsert, MoviesUpdate } from '../pages'
6 |
7 | import 'bootstrap/dist/css/bootstrap.min.css'
8 |
9 | function App() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 | )
24 | }
25 |
26 | export default App
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/components/Links.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import styled from 'styled-components'
4 |
5 | const Collapse = styled.div.attrs({
6 | className: 'collpase navbar-collapse',
7 | })``
8 |
9 | const List = styled.div.attrs({
10 | className: 'navbar-nav mr-auto',
11 | })``
12 |
13 | const Item = styled.div.attrs({
14 | className: 'collpase navbar-collapse',
15 | })``
16 |
17 | class Links extends Component {
18 | render() {
19 | return (
20 |
21 |
22 | Simple MERN Application
23 |
24 |
25 |
26 | -
27 |
28 | List Movies
29 |
30 |
31 | -
32 |
33 | Create Movie
34 |
35 |
36 |
37 |
38 |
39 | )
40 | }
41 | }
42 |
43 | export default Links
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/components/Logo.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styled from 'styled-components'
3 |
4 | import logo from '../logo.svg'
5 |
6 | const Wrapper = styled.a.attrs({
7 | className: 'navbar-brand',
8 | })``
9 |
10 | class Logo extends Component {
11 | render() {
12 | return (
13 |
14 |
15 |
16 | )
17 | }
18 | }
19 |
20 | export default Logo
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/components/NavBar.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styled from 'styled-components'
3 |
4 | import Logo from './Logo'
5 | import Links from './Links'
6 |
7 | const Container = styled.div.attrs({
8 | className: 'container',
9 | })``
10 |
11 | const Nav = styled.nav.attrs({
12 | className: 'navbar navbar-expand-lg navbar-dark bg-dark',
13 | })`
14 | margin-bottom: 20 px;
15 | `
16 |
17 | class NavBar extends Component {
18 | render() {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 | }
28 | }
29 |
30 | export default NavBar
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/components/index.js:
--------------------------------------------------------------------------------
1 |
2 | import Links from './Links'
3 | import Logo from './Logo'
4 | import NavBar from './NavBar'
5 |
6 | export { Links, Logo, NavBar }
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './app'
4 |
5 | ReactDOM.render( , document.getElementById('root'))
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/pages/MoviesInsert.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import api from '../api'
3 |
4 | import styled from 'styled-components'
5 |
6 | const Title = styled.h1.attrs({
7 | className: 'h1',
8 | })``
9 |
10 | const Wrapper = styled.div.attrs({
11 | className: 'form-group',
12 | })`
13 | margin: 0 30px;
14 | `
15 |
16 | const Label = styled.label`
17 | margin: 5px;
18 | `
19 |
20 | const InputText = styled.input.attrs({
21 | className: 'form-control',
22 | })`
23 | margin: 5px;
24 | `
25 |
26 | const Button = styled.button.attrs({
27 | className: `btn btn-primary`,
28 | })`
29 | margin: 15px 15px 15px 5px;
30 | `
31 |
32 | const CancelButton = styled.a.attrs({
33 | className: `btn btn-danger`,
34 | })`
35 | margin: 15px 15px 15px 5px;
36 | `
37 |
38 | class MoviesInsert extends Component {
39 | constructor(props) {
40 | super(props)
41 |
42 | this.state = {
43 | name: '',
44 | rating: '',
45 | time: '',
46 | }
47 | }
48 |
49 | handleChangeInputName = async event => {
50 | const name = event.target.value
51 | this.setState({ name })
52 | }
53 |
54 | handleChangeInputRating = async event => {
55 | const rating = event.target.validity.valid
56 | ? event.target.value
57 | : this.state.rating
58 |
59 | this.setState({ rating })
60 | }
61 |
62 | handleChangeInputTime = async event => {
63 | const time = event.target.value
64 | this.setState({ time })
65 | }
66 |
67 | handleIncludeMovie = async () => {
68 | const { name, rating, time } = this.state
69 | const arrayTime = time.split('/')
70 | const payload = { name, rating, time: arrayTime }
71 |
72 | await api.insertMovie(payload).then(res => {
73 | window.alert(`Movie inserted successfully`)
74 | this.setState({
75 | name: '',
76 | rating: '',
77 | time: '',
78 | })
79 | })
80 | }
81 |
82 | render() {
83 | const { name, rating, time } = this.state
84 | return (
85 |
86 | Create Movie
87 |
88 | Name:
89 |
94 |
95 | Rating:
96 |
106 |
107 | Time:
108 |
113 |
114 | Add Movie
115 | Cancel
116 |
117 | )
118 | }
119 | }
120 |
121 | export default MoviesInsert
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/pages/MoviesList.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactTable from 'react-table'
3 | import api from '../api'
4 |
5 | import styled from 'styled-components'
6 |
7 | import 'react-table/react-table.css'
8 |
9 | const Wrapper = styled.div`
10 | padding: 0 40px 40px 40px;
11 | `
12 |
13 | const Update = styled.div`
14 | color: #ef9b0f;
15 | cursor: pointer;
16 | `
17 |
18 | const Delete = styled.div`
19 | color: #ff0000;
20 | cursor: pointer;
21 | `
22 |
23 | class UpdateMovie extends Component {
24 | updateUser = event => {
25 | event.preventDefault()
26 |
27 | window.location.href = `/movies/update/${this.props.id}`
28 | }
29 |
30 | render() {
31 | return Update
32 | }
33 | }
34 |
35 | class DeleteMovie extends Component {
36 | deleteUser = event => {
37 | event.preventDefault()
38 |
39 | if (
40 | window.confirm(
41 | `Do tou want to delete the movie ${this.props.id} permanently?`,
42 | )
43 | ) {
44 | api.deleteMovieById(this.props.id)
45 | window.location.reload()
46 | }
47 | }
48 |
49 | render() {
50 | return Delete
51 | }
52 | }
53 |
54 | class MoviesList extends Component {
55 | constructor(props) {
56 | super(props)
57 | this.state = {
58 | movies: [],
59 | columns: [],
60 | isLoading: false,
61 | }
62 | }
63 |
64 | componentDidMount = async () => {
65 | this.setState({ isLoading: true })
66 |
67 | await api.getAllMovies().then(movies => {
68 | this.setState({
69 | movies: movies.data.data,
70 | isLoading: false,
71 | })
72 | })
73 | }
74 |
75 | render() {
76 | const { movies, isLoading } = this.state
77 | console.log('TCL: MoviesList -> render -> movies', movies)
78 |
79 | const columns = [
80 | {
81 | Header: 'ID',
82 | accessor: '_id',
83 | filterable: true,
84 | },
85 | {
86 | Header: 'Name',
87 | accessor: 'name',
88 | filterable: true,
89 | },
90 | {
91 | Header: 'Rating',
92 | accessor: 'rating',
93 | filterable: true,
94 | },
95 | {
96 | Header: 'Time',
97 | accessor: 'time',
98 | Cell: props => {props.value.join(' / ')} ,
99 | },
100 | {
101 | Header: '',
102 | accessor: '',
103 | Cell: function(props) {
104 | return (
105 |
106 |
107 |
108 | )
109 | },
110 | },
111 | {
112 | Header: '',
113 | accessor: '',
114 | Cell: function(props) {
115 | return (
116 |
117 |
118 |
119 | )
120 | },
121 | },
122 | ]
123 |
124 | let showTable = true
125 | if (!movies.length) {
126 | showTable = false
127 | }
128 |
129 | return (
130 |
131 | {showTable && (
132 |
140 | )}
141 |
142 | )
143 | }
144 | }
145 |
146 | export default MoviesList
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/pages/MoviesUpdate.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import api from '../api'
3 |
4 | import styled from 'styled-components'
5 |
6 | const Title = styled.h1.attrs({
7 | className: 'h1',
8 | })``
9 |
10 | const Wrapper = styled.div.attrs({
11 | className: 'form-group',
12 | })`
13 | margin: 0 30px;
14 | `
15 |
16 | const Label = styled.label`
17 | margin: 5px;
18 | `
19 |
20 | const InputText = styled.input.attrs({
21 | className: 'form-control',
22 | })`
23 | margin: 5px;
24 | `
25 |
26 | const Button = styled.button.attrs({
27 | className: `btn btn-primary`,
28 | })`
29 | margin: 15px 15px 15px 5px;
30 | `
31 |
32 | const CancelButton = styled.a.attrs({
33 | className: `btn btn-danger`,
34 | })`
35 | margin: 15px 15px 15px 5px;
36 | `
37 |
38 | class MoviesUpdate extends Component {
39 | constructor(props) {
40 | super(props)
41 |
42 | this.state = {
43 | id: this.props.match.params.id,
44 | name: '',
45 | rating: '',
46 | time: '',
47 | }
48 | }
49 |
50 | handleChangeInputName = async event => {
51 | const name = event.target.value
52 | this.setState({ name })
53 | }
54 |
55 | handleChangeInputRating = async event => {
56 | const rating = event.target.validity.valid
57 | ? event.target.value
58 | : this.state.rating
59 |
60 | this.setState({ rating })
61 | }
62 |
63 | handleChangeInputTime = async event => {
64 | const time = event.target.value
65 | this.setState({ time })
66 | }
67 |
68 | handleUpdateMovie = async () => {
69 | const { id, name, rating, time } = this.state
70 | const arrayTime = time.split('/')
71 | const payload = { name, rating, time: arrayTime }
72 |
73 | await api.updateMovieById(id, payload).then(res => {
74 | window.alert(`Movie updated successfully`)
75 | this.setState({
76 | name: '',
77 | rating: '',
78 | time: '',
79 | })
80 | })
81 | }
82 |
83 | componentDidMount = async () => {
84 | const { id } = this.state
85 | const movie = await api.getMovieById(id)
86 |
87 | this.setState({
88 | name: movie.data.data.name,
89 | rating: movie.data.data.rating,
90 | time: movie.data.data.time.join('/'),
91 | })
92 | }
93 |
94 | render() {
95 | const { name, rating, time } = this.state
96 | return (
97 |
98 | Create Movie
99 |
100 | Name:
101 |
106 |
107 | Rating:
108 |
118 |
119 | Time:
120 |
125 |
126 | Update Movie
127 | Cancel
128 |
129 | )
130 | }
131 | }
132 |
133 | export default MoviesUpdate
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import MoviesList from './MoviesList'
2 | import MoviesInsert from './MoviesInsert'
3 | import MoviesUpdate from './MoviesUpdate'
4 |
5 | export { MoviesList, MoviesInsert, MoviesUpdate }
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/client/src/style/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2020-12-27-productionize-mern/client/src/style/index.js
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/docker-compose-dev.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | react-app:
4 | image: react-app-dev
5 | stdin_open: true
6 | ports:
7 | - "3000:3000"
8 | networks:
9 | - mern-app
10 | volumes:
11 | - ./client/:/usr/src/app
12 | - /usr/src/app/node_modules
13 | api-server:
14 | image: api-server
15 | env_file: ./server/config/dev.env
16 | ports:
17 | - "5000:5000"
18 | networks:
19 | - mern-app
20 | volumes:
21 | - ./server/:/usr/src/app
22 | - /usr/src/app/node_modules
23 | depends_on:
24 | - mongo
25 | mongo:
26 | image: mongo:4.4-bionic
27 | ports:
28 | - "27017:27017"
29 | networks:
30 | - mern-app
31 | volumes:
32 | - mongo-data:/data/db
33 | networks:
34 | mern-app:
35 | driver: bridge
36 | volumes:
37 | mongo-data:
38 | driver: local
39 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/docker-compose-production.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | react-app:
4 | image: react-app-production:${ENV}
5 | restart: unless-stopped
6 | ports:
7 | - "80:80"
8 | - "443:443"
9 | volumes:
10 | - caddy-data:/data
11 | - caddy-config:/config
12 | networks:
13 | - mern-app
14 | api-server:
15 | image: api-server
16 | restart: unless-stopped
17 | env_file: ./server/config/${ENV}.env
18 | ports:
19 | - "5000:5000"
20 | networks:
21 | - mern-app
22 | networks:
23 | mern-app:
24 | driver: bridge
25 | volumes:
26 | mongo-data:
27 | driver: local
28 | caddy-data:
29 | driver: local
30 | caddy-config:
31 | driver: local
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/sequence.md:
--------------------------------------------------------------------------------
1 | **Part 1: Dockerize**
2 | 1) Run baseline application
3 | ```
4 | yarn start
5 | yarn start
6 | brew services start mongodb-community@4.4
7 | ```
8 |
9 | 2) Move Mongo to container
10 |
11 | `docker run -p 27017:27017 mongo:4.4-bionic`
12 |
13 | 3) Dockerize react client [Dockerfile](./client/Dockerfile.dev)
14 | 4) Dockerize api server [Dockerfile](./server/Dockerfile)
15 | 5) Set up docker compose [docker-compose.yml](./docker-compose-dev.yml)
16 | 6) Enable hot reloading by mounting in src
17 |
18 | **Part 2: Productionize**
19 | 1) add restart: unless-stopped
20 | 2) Break out separate docker-compose files
21 | 3) Move DB to Atlas
22 | 4) Update client Dockerfile to build production version
23 | 5) Use Caddy to serve front end files
24 | 6) Parameterize connection strings
25 | 7) Split local and production configurations
26 |
27 | **Part 3: Deployment**
28 | 1) Create Digital Ocean VM
29 | 2) Configure DNS
30 | 3) Configure network access in Atlas
31 | 4) Configure Caddy
32 | 5) Deploy!
33 |
34 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/server/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | *.env
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14-slim
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY ./package.json ./
6 | COPY ./yarn.lock ./
7 |
8 | RUN yarn
9 |
10 | COPY . .
11 |
12 | EXPOSE 5000
13 |
14 | CMD [ "yarn", "start" ]
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/server/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | docker build -t api-server .
3 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/server/config/dev.env:
--------------------------------------------------------------------------------
1 | MONGO_URI='mongodb://mongo:27017/cinema'
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/server/controllers/movie-ctrl.js:
--------------------------------------------------------------------------------
1 | const Movie = require('../models/movie-model')
2 |
3 | createMovie = (req, res) => {
4 | const body = req.body
5 |
6 | if (!body) {
7 | return res.status(400).json({
8 | success: false,
9 | error: 'You must provide a movie',
10 | })
11 | }
12 |
13 | const movie = new Movie(body)
14 |
15 | if (!movie) {
16 | return res.status(400).json({ success: false, error: err })
17 | }
18 |
19 | movie
20 | .save()
21 | .then(() => {
22 | return res.status(201).json({
23 | success: true,
24 | id: movie._id,
25 | message: 'Movie created!',
26 | })
27 | })
28 | .catch(error => {
29 | return res.status(400).json({
30 | error,
31 | message: 'Movie not created!',
32 | })
33 | })
34 | }
35 |
36 | updateMovie = async (req, res) => {
37 | const body = req.body
38 |
39 | if (!body) {
40 | return res.status(400).json({
41 | success: false,
42 | error: 'You must provide a body to update',
43 | })
44 | }
45 |
46 | Movie.findOne({ _id: req.params.id }, (err, movie) => {
47 | if (err) {
48 | return res.status(404).json({
49 | err,
50 | message: 'Movie not found!',
51 | })
52 | }
53 | movie.name = body.name
54 | movie.time = body.time
55 | movie.rating = body.rating
56 | movie
57 | .save()
58 | .then(() => {
59 | return res.status(200).json({
60 | success: true,
61 | id: movie._id,
62 | message: 'Movie updated!',
63 | })
64 | })
65 | .catch(error => {
66 | return res.status(404).json({
67 | error,
68 | message: 'Movie not updated!',
69 | })
70 | })
71 | })
72 | }
73 |
74 | deleteMovie = async (req, res) => {
75 | await Movie.findOneAndDelete({ _id: req.params.id }, (err, movie) => {
76 | if (err) {
77 | return res.status(400).json({ success: false, error: err })
78 | }
79 |
80 | if (!movie) {
81 | return res
82 | .status(404)
83 | .json({ success: false, error: `Movie not found` })
84 | }
85 |
86 | return res.status(200).json({ success: true, data: movie })
87 | }).catch(err => console.log(err))
88 | }
89 |
90 | getMovieById = async (req, res) => {
91 | await Movie.findOne({ _id: req.params.id }, (err, movie) => {
92 | if (err) {
93 | return res.status(400).json({ success: false, error: err })
94 | }
95 |
96 | if (!movie) {
97 | return res
98 | .status(404)
99 | .json({ success: false, error: `Movie not found` })
100 | }
101 | return res.status(200).json({ success: true, data: movie })
102 | }).catch(err => console.log(err))
103 | }
104 |
105 | getMovies = async (req, res) => {
106 | await Movie.find({}, (err, movies) => {
107 | if (err) {
108 | return res.status(400).json({ success: false, error: err })
109 | }
110 | if (!movies.length) {
111 | return res
112 | .status(404)
113 | .json({ success: false, error: `Movie not found` })
114 | }
115 | return res.status(200).json({ success: true, data: movies })
116 | }).catch(err => console.log(err))
117 | }
118 |
119 | module.exports = {
120 | createMovie,
121 | updateMovie,
122 | deleteMovie,
123 | getMovies,
124 | getMovieById,
125 | }
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/server/db/index.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const connectionString = process.env.MONGO_URI;
4 |
5 | mongoose.connect(connectionString, { useNewUrlParser: true }).catch((e) => {
6 | console.error('Connection error', e.message);
7 | });
8 |
9 | const db = mongoose.connection;
10 |
11 | module.exports = db;
12 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/server/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const bodyParser = require('body-parser')
3 | const cors = require('cors')
4 | require('dotenv').config()
5 |
6 | const db = require('./db')
7 | const movieRouter = require('./routes/movie-router')
8 |
9 | const app = express()
10 | const apiPort = 5000
11 |
12 | app.use(bodyParser.urlencoded({ extended: true }))
13 | app.use(cors())
14 | app.use(bodyParser.json())
15 |
16 | db.on('error', console.error.bind(console, 'MongoDB connection error:'))
17 |
18 | app.get('/', (req, res) => {
19 | res.send('Hello World!')
20 | })
21 |
22 | app.use('/api', movieRouter)
23 |
24 | app.listen(apiPort, () => console.log(`Server running on port ${apiPort}`))
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/server/models/movie-model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 | const Schema = mongoose.Schema
3 |
4 | const Movie = new Schema(
5 | {
6 | name: { type: String, required: true },
7 | time: { type: [String], required: true },
8 | rating: { type: Number, required: true },
9 | },
10 | { timestamps: true },
11 | )
12 |
13 | module.exports = mongoose.model('movies', Movie)
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "start": "node index.js"
8 | },
9 | "dependencies": {
10 | "body-parser": "^1.19.0",
11 | "cors": "^2.8.5",
12 | "dotenv": "^8.2.0",
13 | "express": "^4.17.1",
14 | "mongoose": "^5.9.29",
15 | "nodemon": "^2.0.4"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/2020-12-27-productionize-mern/server/routes/movie-router.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 |
3 | const MovieCtrl = require('../controllers/movie-ctrl')
4 |
5 | const router = express.Router()
6 |
7 | router.post('/movie', MovieCtrl.createMovie)
8 | router.put('/movie/:id', MovieCtrl.updateMovie)
9 | router.delete('/movie/:id', MovieCtrl.deleteMovie)
10 | router.get('/movie/:id', MovieCtrl.getMovieById)
11 | router.get('/movies', MovieCtrl.getMovies)
12 |
13 |
14 | module.exports = router
--------------------------------------------------------------------------------
/2021-01-27-multi-arch-docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12
2 | WORKDIR /usr/src/app
3 |
4 | COPY package*.json ./
5 | RUN npm install
6 |
7 | COPY server.js ./
8 |
9 | CMD ["server.js" ]
10 |
--------------------------------------------------------------------------------
/2021-01-27-multi-arch-docker/Makefile:
--------------------------------------------------------------------------------
1 | login:
2 | docker login
3 |
4 | list-builders:
5 | docker buildx ls
6 |
7 | initialize-builder:
8 | docker buildx create --name mybuilder
9 | docker buildx use mybuilder
10 | docker buildx inspect --boostrap
11 |
12 | build:
13 | docker buildx build \
14 | --platform linux/amd64,linux/arm64,linux/arm/v7 \
15 | -t sidpalas/multi-arch-test:latest \
16 | --push \
17 | .
18 |
19 | CONTAINER_NAME:=multi-arch-test
20 | run:
21 | docker run -i -d --rm \
22 | -p 8080:8080 \
23 | --name $(CONTAINER_NAME) \
24 | sidpalas/multi-arch-test
25 |
26 | ARM_SHA?=660432aec93b84c61d24541e5cf135491829df01ac900a20de325f8726f6118c
27 | run-arm:
28 | docker run -i -d --rm \
29 | -p 8080:8080 \
30 | --name $(CONTAINER_NAME) \
31 | sidpalas/multi-arch-test@sha256:$(ARM_SHA)
32 |
33 | stop:
34 | docker stop $(CONTAINER_NAME)
35 |
36 | inspect:
37 | docker buildx imagetools inspect sidpalas/multi-arch-test:latest
38 |
--------------------------------------------------------------------------------
/2021-01-27-multi-arch-docker/README.md:
--------------------------------------------------------------------------------
1 | # How to Build Multi-Architecture Docker Images with BuildX | Deploy containers to x86 and ARM!
2 |
3 | Video: https://youtu.be/hWSHtHasJUI
4 |
5 | Building and running a simple Node.js application with a variety of architectures using docker buildx
--------------------------------------------------------------------------------
/2021-01-27-multi-arch-docker/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-world",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC"
11 | }
12 |
--------------------------------------------------------------------------------
/2021-01-27-multi-arch-docker/server.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 |
3 | const hostname = '0.0.0.0';
4 | const port = 8080;
5 |
6 | const server = http.createServer((req, res) => {
7 | res.statusCode = 200;
8 | res.setHeader('Content-Type', 'text/plain');
9 | res.end(`Hello from node.js in ${process.arch}!`);
10 | });
11 |
12 | server.listen(port, hostname, () => {
13 | console.log(`Server running at http://${hostname}:${port}/`);
14 | });
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/Makefile:
--------------------------------------------------------------------------------
1 | # NODE
2 | build-node-naive:
3 | docker build -t 00-node-naive -f ./node/Dockerfile.naive ./node
4 |
5 | build-node-slim:
6 | docker build -t 01-node-slim -f ./node/Dockerfile.slim ./node
7 |
8 | build-node-alpine:
9 | docker build -t 02-node-alpine -f ./node/Dockerfile.alpine ./node
10 |
11 | build-all-node:
12 | $(MAKE) build-node-naive
13 | $(MAKE) build-node-slim
14 | $(MAKE) build-node-alpine
15 |
16 | # GO
17 | build-go-naive:
18 | docker build -t 03-go-naive -f ./go/Dockerfile.naive ./go
19 |
20 | build-go-multi:
21 | docker build -t 04-go-alpine -f ./go/Dockerfile.alpine-multi ./go
22 |
23 | build-go-scratch:
24 | docker build -t 05-go-scratch -f ./go/Dockerfile.scratch ./go
25 |
26 | build-all-go:
27 | $(MAKE) build-go-naive
28 | $(MAKE) build-go-multi
29 | $(MAKE) build-go-scratch
30 |
31 |
32 | # C
33 | build-c:
34 | docker build -t c-scratch ./c
35 |
36 | # ASM
37 | build-asm:
38 | docker build -t 06-asm-scratch ./asm
39 |
40 | build-all:
41 | $(MAKE) build-all-node
42 | $(MAKE) build-all-go
43 | $(MAKE) build-c
44 | $(MAKE) build-asm
45 |
46 | run:
47 | docker run -d --rm -p 8081:8081 --name server go-scratch
48 |
49 | stop:
50 | docker kill server
51 |
52 | list-image-sizes:
53 | docker images --format \
54 | "{{.ID}}\t{{.Size}}\t{{.Repository}}" | \
55 | sort -k 3 -h | \
56 | grep 'node-\|go-\|c-\|asm-'
57 |
58 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/README.md:
--------------------------------------------------------------------------------
1 | # TINY CONTAINER CHALLENGE: Building the World's Smallest Docker Container!
2 |
3 | Video: https://youtu.be/VG8rZIE8ET8
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2021-04-03-smallest-possible-container/asm/.DS_Store
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/.dockerignore:
--------------------------------------------------------------------------------
1 | Dockerfile
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/.gitignore:
--------------------------------------------------------------------------------
1 | asmttpd
2 | *.o
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/Dockerfile:
--------------------------------------------------------------------------------
1 | ### build stage ###
2 | FROM ubuntu:18.04 as builder
3 | RUN apt update
4 | RUN apt install -y make yasm as31 nasm binutils
5 | COPY . .
6 | RUN make release
7 |
8 | ### run stage ###
9 | FROM scratch
10 | COPY --from=builder /asmttpd /asmttpd
11 | COPY /web_root/index.html /web_root/index.html
12 | CMD ["/asmttpd", "/web_root", "8080"]
13 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/Makefile:
--------------------------------------------------------------------------------
1 | # asmttpd - Web server for Linux written in amd64 assembly. \
2 | Copyright (C) 2014 nemasu \
3 | \
4 | This file is part of asmttpd. \
5 | \
6 | asmttpd is free software: you can redistribute it and/or modify \
7 | it under the terms of the GNU General Public License as published by \
8 | the Free Software Foundation, either version 2 of the License, or \
9 | (at your option) any later version. \
10 | \
11 | asmttpd is distributed in the hope that it will be useful, \
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of \
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the \
14 | GNU General Public License for more details. \
15 | \
16 | You should have received a copy of the GNU General Public License \
17 | along with asmttpd. If not, see .
18 |
19 | all: main
20 |
21 | release: http.asm constants.asm bss.asm data.asm macros.asm main.asm mutex.asm string.asm syscall.asm
22 | yasm -f elf64 -a x86 main.asm -o main.o
23 | ld main.o -o asmttpd
24 | strip -s asmttpd
25 |
26 | main.o: http.asm constants.asm bss.asm data.asm macros.asm main.asm mutex.asm string.asm syscall.asm
27 | yasm -g dwarf2 -f elf64 -a x86 main.asm -o main.o
28 | main: main.o
29 | ld main.o -o asmttpd
30 | clean:
31 | rm -rf main.o asmttpd
32 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/README.md:
--------------------------------------------------------------------------------
1 | asmttpd
2 | =======
3 |
4 | Web server for Linux written in amd64 assembly.
5 |
6 | Features:
7 | * Multi-threaded.
8 | * No libraries required ( only 64-bit Linux ).
9 | * Very small binary, roughly 6 KB.
10 | * Quite fast.
11 |
12 | What works:
13 | * Serving files from specified document root.
14 | * HEAD requests.
15 | * 200, 206, 404, 400, 413, 416
16 | * Content-types: xml, html, xhtml, gif, png, jpeg, css, js, svg, and octet-stream.
17 |
18 | Planned Features:
19 | * Directory listing.
20 |
21 | Current Limitations / Known Issues
22 | =======
23 | * Sendfile can hang if GET is cancelled.
24 |
25 | Installation
26 | =======
27 |
28 | Run `make` or `make release` for non-debug version.
29 |
30 | You will need `yasm` installed.
31 |
32 | Usage
33 | =======
34 |
35 | `./asmttpd /path/to/web_root port_number`
36 |
37 | Example: `./asmttpd ./web_root 8080`
38 |
39 | Changes
40 | =======
41 | 2021-01-15 : asmttpd - 0.4.5
42 |
43 | * string_contains bugfix.
44 |
45 | 2019-04-22 : asmttpd - 0.4.4
46 |
47 | * Added SVG support.
48 |
49 | 2019-01-24 : asmttpd - 0.4.3
50 |
51 | * Added port number as parameter.
52 |
53 | 2017-10-18 : asmttpd - 0.4.2
54 |
55 | * Set REUSEADDR.
56 |
57 | 2017-10-17 : asmttpd - 0.4.1
58 |
59 | * Stack address bug fix.
60 |
61 | 2016-10-31 : asmttpd - 0.4
62 |
63 | * HEAD support.
64 |
65 | 2014-07-14 : asmttpd - 0.3
66 |
67 | * Added default document support.
68 |
69 | 2014-02-10 : asmttpd - 0.2
70 |
71 | * Added 400, 413, 416 responses.
72 | * Fixed header processing bug.
73 |
74 | 2014-02-07 : asmttpd - 0.1.1
75 |
76 | * Fixed 206 max length bug.
77 | * Commented out simple request logging, uncomment in main.asm to enable.
78 |
79 | 2014-02-06 : asmttpd - 0.1
80 |
81 | * Fixed SIGPIPE when transfer is cancelled.
82 | * Added a more useful error on bind failure.
83 | * Fixed 206 size calculation.
84 | * Combined seek & get file size system calls.
85 |
86 | 2014-02-05 : asmttpd - 0.09
87 |
88 | * Issue #8 fix. Crashes on long request paths.
89 |
90 | 2014-02-04 : asmttpd - 0.08
91 |
92 | * Added TCP corking.
93 |
94 | 2014-02-04 : asmttpd - 0.07
95 |
96 | * Removed thread pool after benchmarking, changed to an accept-per-thread model.
97 |
98 | 2014-02-04 : asmttpd - 0.06
99 |
100 | * Worker thread stack corruption bug fix.
101 |
102 | 2014-02-04 : asmttpd - 0.05
103 |
104 | * Changed 200 and 206 implementation to use sendfile system call.
105 | * Got rid of read/write buffer, changed request read buffer to standard 8KB.
106 |
107 | 2014-02-03 : asmttpd - 0.04
108 |
109 | * 200 now streams full amount
110 |
111 |
112 | 2014-02-01 : asmttpd - 0.03
113 |
114 | * Files are split if too large to fit into buffer.
115 | * Added 206 responses with Content-Range handling
116 |
117 |
118 | 2014-01-30 : asmttpd - 0.02
119 |
120 | * Added xml, xhtml, gif, png, jpeg, css, and javascript content types.
121 | * Changed thread memory size to something reasonable. You can tweak it according to available memory. See comments in main.asm
122 | * Added simple request logging.
123 | * Added removal of '../' in URL.
124 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/bss.asm:
--------------------------------------------------------------------------------
1 | ;asmttpd - Web server for Linux written in amd64 assembly.
2 | ;Copyright (C) 2014 nemasu
3 | ;
4 | ;This file is part of asmttpd.
5 | ;
6 | ;asmttpd is free software: you can redistribute it and/or modify
7 | ;it under the terms of the GNU General Public License as published by
8 | ;the Free Software Foundation, either version 2 of the License, or
9 | ;(at your option) any later version.
10 | ;
11 | ;asmttpd is distributed in the hope that it will be useful,
12 | ;but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | ;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | ;GNU General Public License for more details.
15 | ;
16 | ;You should have received a copy of the GNU General Public License
17 | ;along with asmttpd. If not, see .
18 | listen_socket: resq 1
19 | listen_port: resw 1
20 | one_constant: resq 1
21 | directory_path: resq 1
22 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/constants.asm:
--------------------------------------------------------------------------------
1 | ;asmttpd - Web server for Linux written in amd64 assembly.
2 | ;Copyright (C) 2014 nemasu
3 | ;
4 | ;This file is part of asmttpd.
5 | ;
6 | ;asmttpd is free software: you can redistribute it and/or modify
7 | ;it under the terms of the GNU General Public License as published by
8 | ;the Free Software Foundation, either version 2 of the License, or
9 | ;(at your option) any later version.
10 | ;
11 | ;asmttpd is distributed in the hope that it will be useful,
12 | ;but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | ;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | ;GNU General Public License for more details.
15 | ;
16 | ;You should have received a copy of the GNU General Public License
17 | ;along with asmttpd. If not, see .
18 |
19 | ;Constants
20 | %define FD_STDOUT 0x1
21 | %define THREAD_STACK_SIZE 16384
22 | %define SIGCHILD 0x11 ;SIGCHILD signal constant
23 | %define SIGHUP 1 ;Hangup (POSIX).
24 | %define SIGINT 2 ;Interrupt (ANSI).
25 | %define SIGQUIT 3 ;Quit (POSIX).
26 | %define SIGPIPE 13;broken pipe
27 | %define SIGTERM 15; Default kill signal
28 | %define SIGIGN 1;Ignore signal
29 | %define SA_RESTORER 0x04000000 ;Required for x86_64 sigaction
30 | %define QUEUE_SIZE 40960 ; in bytes, 40960 is about 5120 fds.
31 | %define THREAD_BUFFER_SIZE 8192 ; 8KB recv buffer
32 | %define URL_LENGTH_LIMIT 2000
33 | %define DIRECTORY_LENGTH_LIMIT 100
34 |
35 | ;Request Types
36 | %define REQ_UNK 0
37 | %define REQ_GET 1
38 | %define REQ_HEAD 2
39 |
40 | ;Flags
41 | %define MMAP_PROT_READ 0x1
42 | %define MMAP_PROT_WRITE 0x2
43 | %define MMAP_MAP_PRIVATE 0x2
44 | %define MMAP_MAP_ANON 0x20
45 | %define MMAP_MAP_GROWSDOWN 0x100
46 |
47 | %define CLONE_VM 0x100 ;Same memory space
48 | %define CLONE_FS 0x200 ;Same file system information
49 | %define CLONE_FILES 0x400 ;Share file descriptors
50 | %define CLONE_SIGHAND 0x800 ;Share signal handlers
51 | %define CLONE_THREAD 0x10000 ;Same thread group ( same process )
52 |
53 | %define OPEN_RDONLY 00
54 | %define OPEN_DIRECTORY 0x10000 ; Open will fail if path is not a directory
55 |
56 | %define AF_INET 2
57 | %define SOCK_STREAM 1
58 | %define PROTO_TCP 6
59 |
60 | %define LSEEK_SET 0 ; seek to offset bytes
61 | %define LSEEK_END 2 ; seek to end plus offset
62 |
63 | %define LEVEL_SOL_TCP 1
64 | %define LEVEL_IPPROTO_TCP 6
65 | %define SOCKOPT_TCP_REUSEADDR 2
66 | %define SOCKOPT_TCP_CORK 3
67 |
68 | ;Internal Constants
69 | %define CONTENT_TYPE_HTML 0
70 | %define CONTENT_TYPE_OCTET_STREAM 1
71 | %define CONTENT_TYPE_CSS 2
72 | %define CONTENT_TYPE_JAVASCRIPT 3
73 | %define CONTENT_TYPE_XHTML 4
74 | %define CONTENT_TYPE_XML 5
75 | %define CONTENT_TYPE_GIF 6
76 | %define CONTENT_TYPE_PNG 7
77 | %define CONTENT_TYPE_JPEG 8
78 | %define CONTENT_TYPE_SVG 9
79 |
80 | ;System Call Values
81 | %define SYS_WRITE 1 ;int fd, const void *buf, size_t count
82 | %define SYS_LSEEK 8 ;int fd, off_t offset, int whence
83 | %define SYS_MMAP 9 ;void *addr, size_t length, int prot, int flags, int fd, off_t offset
84 | %define SYS_CLONE 56 ;unsigned long clone_flags, unsigned long newsp, void ___user *parent_tid, void __user *child_tid, struct pt_regs *regs
85 | %define SYS_EXIT 60 ;int status
86 | %define SYS_EXIT_GROUP 231 ;int status
87 | %define SYS_NANOSLEEP 35 ;const struct timespec *req, struct timespec *rem
88 | %define SYS_RT_SIGACTION 13 ;int sig,const struct sigaction __user * act,struct sigaction __user * oact,size_t sigsetsize
89 | %define SYS_SOCKET 41 ;int domain, int type, int protocol
90 | %define SYS_ACCEPT 43 ;int sockfd, struct sockaddr *addr, socklen_t *addrlen
91 | %define SYS_SENDTO 44 ;int sockfd, const void *buf, size_t len, int flags, ...
92 | %define SYS_RECVFROM 45 ;int sockfd, void *buf, size_t len, int flags
93 | %define SYS_BIND 49 ;int sockfd, const struct sockaddr *addr, socklen_t addrlen
94 | %define SYS_LISTEN 50 ;int sockfd, int backlog
95 | %define SYS_SELECT 23 ;int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout
96 | %define SYS_GETDENTS 78 ;unsigned int fd, struct linux_dirent *dirp, unsigned int count
97 | %define SYS_OPEN 2 ;const char *pathname, int flags, mode_t mode
98 | %define SYS_CLOSE 3 ;unsigned int fd
99 | %define SYS_SENDFILE 40 ;int out_fd, int in_fd, off_t *offset, size_t count
100 | %define SYS_SETSOCKOPT 54; int sockfd, int level, int optname,const void *optval, socklen_t optlen
101 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/data.asm:
--------------------------------------------------------------------------------
1 | ;asmttpd - Web server for Linux written in amd64 assembly.
2 | ;Copyright (C) 2014 nemasu
3 | ;
4 | ;This file is part of asmttpd.
5 | ;
6 | ;asmttpd is free software: you can redistribute it and/or modify
7 | ;it under the terms of the GNU General Public License as published by
8 | ;the Free Software Foundation, either version 2 of the License, or
9 | ;(at your option) any later version.
10 | ;
11 | ;asmttpd is distributed in the hope that it will be useful,
12 | ;but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | ;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | ;GNU General Public License for more details.
15 | ;
16 | ;You should have received a copy of the GNU General Public License
17 | ;along with asmttpd. If not, see .
18 |
19 | struc sockaddr_in
20 | sin_family: resw 1
21 | sin_port: resw 1
22 | sin_addr: resd 1
23 | endstruc
24 |
25 | sa: istruc sockaddr_in
26 | at sin_family, dw AF_INET
27 | at sin_port, dw 0
28 | at sin_addr, dd 0 ;INADDR_ANY
29 | iend
30 |
31 | request_type dq 0
32 | request_offset dq 0
33 |
34 | timeval: ;struct
35 | tv_sec dq 0
36 | tv_usec dq 0
37 | sigaction: ;struct
38 | sa_handler dq 0
39 | sa_flags dq SA_RESTORER ; also dq, because padding
40 | sa_restorer dq 0
41 | sa_mask dq 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
42 |
43 | ;Strings
44 | in_enter db "In Enter:",0x00
45 | in_enter_len equ $ - in_enter
46 | in_exit db "In Exit:", 0x00
47 | in_exit_len equ $ - in_exit
48 | new_line db 0x0a
49 | start_text db "asmttpd - ",ASMTTPD_VERSION,0x0a,0x00
50 | start_text_len equ $ - start_text
51 | msg_bind_error db "Error - Bind() failed. Check if port is in use or you have sufficient privileges.",0x00
52 | msg_bind_error_len equ $ - msg_bind_error
53 | msg_error db "An error has occured, exiting",0x00
54 | msg_error_len equ $ - msg_error
55 | msg_help db "Usage: ./asmttpd /path/to/directory port",0x00
56 | msg_help_len equ $ - msg_help
57 | msg_not_a_directory dd "Error: Specified document root is not a directory",0x00
58 | msg_not_a_directory_len equ $ - msg_not_a_directory
59 | msg_request_log db 0x0a,"Request: ",0x00
60 | msg_request_log_len equ $ - msg_request_log
61 |
62 | header_range_search db "Range: ",0x00
63 | header_range_search_len equ $ - header_range_search
64 |
65 | find_bytes_range db "bytes=",0x00
66 | find_bytes_range_len equ $ - find_bytes_range
67 |
68 | filter_prev_dir db "../",0x00
69 | filter_prev_dir_len equ $ - filter_prev_dir
70 |
71 | crlfx2 db 0x0d,0x0a,0x0d,0x0a,0x00
72 | crlfx2_len equ $ - crlfx2
73 |
74 | crlf db 0x0d,0x0a,0x00
75 | crlf_len equ $ - crlf
76 |
77 | char_slash db "/",0x00
78 | char_hyphen db "-",0x00
79 |
80 | ;HTTP
81 | http_200 db "HTTP/1.1 200 OK",0x0d,0x0a,0x00
82 | http_200_len equ $ - http_200
83 | http_206 db "HTTP/1.1 206 Partial Content",0x0d,0x0a,0x00
84 | http_206_len equ $ - http_206
85 | http_404 db "HTTP/1.1 404 Not Found",0x0d,0x0a,0x00
86 | http_404_len equ $ - http_404
87 | http_404_text db "I'm sorry, Dave. I'm afraid I can't do that. 404 Not Found",0x00
88 | http_404_text_len equ $ - http_404_text
89 | http_400 db "HTTP/1.1 400 Bad Request",0x0d,0x0a,0x00
90 | http_400_len equ $ - http_400
91 | http_413 db "HTTP/1.1 413 Request Entity Too Large",0x0d,0x0a,0x00
92 | http_413_len equ $ - http_413
93 | http_416 db "HTTP/1.1 416 Requested Range Not Satisfiable",0x0d,0x0a,0x00
94 | http_416_len equ $ - http_416
95 |
96 | server_header db "Server: asmttpd/",ASMTTPD_VERSION,0x0d,0x0a,0x00
97 | server_header_len equ $ - server_header
98 |
99 | range_header db "Accept-Ranges: bytes",0x0d,0x0a,0x00
100 | range_header_len equ $ - range_header
101 |
102 | content_range db "Content-Range: bytes ",0x00
103 | content_range_len equ $ - content_range ;Content-Range: bytes 200-1000/3000
104 |
105 | content_length db "Content-Length: ",0x00 ;Content-Length: 800
106 | content_length_len equ $ - content_length
107 |
108 | connection_header db "Connection: close",0x0d,0x0a,0x00
109 | connection_header_len equ $ - connection_header
110 |
111 | content_type db "Content-Type: ",0x00
112 | content_type_len equ $ - content_type
113 |
114 | ;Content-Type
115 | content_type_html db "text/html",0x0d,0x0a,0x00
116 | content_type_html_len equ $ - content_type_html
117 |
118 | content_type_octet_stream db "application/octet-stream",0x0d,0x0a,0x00
119 | content_type_octet_stream_len equ $ - content_type_octet_stream
120 |
121 | content_type_xhtml db "application/xhtml+xml",0x0d,0x0a,0x00
122 | content_type_xhtml_len equ $ - content_type_xhtml
123 |
124 | content_type_xml db "text/xml",0x0d,0x0a,0x00
125 | content_type_xml_len equ $ - content_type_xml
126 |
127 | content_type_javascript db "application/javascript",0x0d,0x0a,0x00
128 | content_type_javascript_len equ $ - content_type_javascript
129 |
130 | content_type_css db "text/css",0x0d,0x0a,0x00
131 | content_type_css_len equ $ - content_type_css
132 |
133 | content_type_jpeg db "image/jpeg",0x0d,0x0a,0x00
134 | content_type_jpeg_len equ $ - content_type_jpeg
135 |
136 | content_type_png db "image/png",0x0d,0x0a,0x00
137 | content_type_png_len equ $ - content_type_png
138 |
139 | content_type_gif db "image/gif",0x0d,0x0a,0x00
140 | content_type_gif_len equ $ - content_type_gif
141 |
142 | content_type_svg db "image/svg+xml",0x0d,0x0a,0x00
143 | content_type_svg_len equ $ - content_type_svg
144 |
145 | default_document db "/index.html",0x00
146 | default_document_len equ $ - default_document
147 |
148 | ;Content extension
149 | extension_html db ".html",0x00
150 | extension_htm db ".htm" ,0x00
151 | extension_javascript db ".js", 0x00
152 | extension_css db ".css", 0x00
153 | extension_xhtml db ".xhtml",0x00
154 | extension_xml db ".xml",0x00
155 | extension_gif db ".gif",0x00
156 | extension_jpg db ".jpg",0x00
157 | extension_jpeg db ".jpeg",0x00
158 | extension_png db ".png",0x00
159 | extension_svg db ".svg",0x00
160 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/debug.asm:
--------------------------------------------------------------------------------
1 | ;asmttpd - Web server for Linux written in amd64 assembly.
2 | ;Copyright (C) 2014 nemasu
3 | ;
4 | ;This file is part of asmttpd.
5 | ;
6 | ;asmttpd is free software: you can redistribute it and/or modify
7 | ;it under the terms of the GNU General Public License as published by
8 | ;the Free Software Foundation, either version 2 of the License, or
9 | ;(at your option) any later version.
10 | ;
11 | ;asmttpd is distributed in the hope that it will be useful,
12 | ;but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | ;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | ;GNU General Public License for more details.
15 | ;
16 | ;You should have received a copy of the GNU General Public License
17 | ;along with asmttpd. If not, see .
18 |
19 | ;hint, use these with GDB
20 | ;set follow-fork-mode child
21 |
22 | section .bss
23 | printbuffer: resb 1024;debug
24 |
25 | section .text
26 | print_rdi:
27 |
28 | stackpush
29 | push rax
30 | push rbx
31 | push rcx
32 |
33 |
34 | call get_number_of_digits
35 | mov rcx, rax ;
36 |
37 | inc rax ;include NL char in length
38 | push rax; save length for syscall
39 |
40 | ;add lf
41 | mov rdx, 0xa
42 | mov [printbuffer+rcx], dl
43 | dec rcx
44 |
45 | mov rax, rdi ; value to print
46 | xor rdx, rdx ; zero other half
47 | mov rbx, 10
48 |
49 | print_rdi_start:
50 | xor rdx, rdx ; zero other half
51 | div rbx ; divide by 10
52 |
53 | add rdx, 0x30
54 | mov [printbuffer+rcx], dl
55 | dec rcx
56 | cmp rax, 9
57 | ja print_rdi_start
58 |
59 | add rax, 0x30 ;last digit
60 | mov [printbuffer+rcx], al
61 |
62 | pop rcx ;restore original length
63 |
64 | mov rdi, printbuffer
65 | mov rsi, rcx
66 | call sys_write
67 |
68 | pop rcx
69 | pop rbx
70 | pop rax
71 | stackpop
72 | ret
73 |
74 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/macros.asm:
--------------------------------------------------------------------------------
1 | ;asmttpd - Web server for Linux written in amd64 assembly.
2 | ;Copyright (C) 2014 nemasu
3 | ;
4 | ;This file is part of asmttpd.
5 | ;
6 | ;asmttpd is free software: you can redistribute it and/or modify
7 | ;it under the terms of the GNU General Public License as published by
8 | ;the Free Software Foundation, either version 2 of the License, or
9 | ;(at your option) any later version.
10 | ;
11 | ;asmttpd is distributed in the hope that it will be useful,
12 | ;but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | ;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | ;GNU General Public License for more details.
15 | ;
16 | ;You should have received a copy of the GNU General Public License
17 | ;along with asmttpd. If not, see .
18 |
19 | ; Simple Macros
20 | %macro stackpush 0
21 | push rdi
22 | push rsi
23 | push rdx
24 | push r10
25 | push r8
26 | push r9
27 | push rbx
28 | push rcx
29 | %endmacro
30 |
31 | %macro stackpop 0
32 | pop rcx
33 | pop rbx
34 | pop r9
35 | pop r8
36 | pop r10
37 | pop rdx
38 | pop rsi
39 | pop rdi
40 | %endmacro
41 |
42 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/mutex.asm:
--------------------------------------------------------------------------------
1 | ;asmttpd - Web server for Linux written in amd64 assembly.
2 | ;Copyright (C) 2014 nemasu
3 | ;
4 | ;This file is part of asmttpd.
5 | ;
6 | ;asmttpd is free software: you can redistribute it and/or modify
7 | ;it under the terms of the GNU General Public License as published by
8 | ;the Free Software Foundation, either version 2 of the License, or
9 | ;(at your option) any later version.
10 | ;
11 | ;asmttpd is distributed in the hope that it will be useful,
12 | ;but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | ;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | ;GNU General Public License for more details.
15 | ;
16 | ;You should have received a copy of the GNU General Public License
17 | ;along with asmttpd. If not, see .
18 |
19 | mutex_leave:
20 | stackpush
21 |
22 | mov rax, 1
23 | mov rdi, 0
24 | lock cmpxchg [queue_futex], rdi ; 1 -> 0 case
25 | cmp rax, 1
26 | je mutex_leave_end
27 |
28 | mov QWORD [queue_futex], 0
29 | mov rdi, 1
30 | call sys_futex_wake
31 |
32 | mutex_leave_end:
33 | stackpop
34 | ret
35 |
36 | mutex_enter:
37 | stackpush
38 |
39 | mov rax, 0
40 | mov rdi, 1
41 | lock cmpxchg [queue_futex], rdi
42 | cmp rax, 0
43 | je mutex_enter_end
44 |
45 | mutex_enter_begin:
46 | cmp rax, 2
47 | je mutex_enter_wait
48 | mov rax, 1
49 | mov rdi, 2
50 | lock cmpxchg [queue_futex], rdi
51 | cmp rax, 1
52 | je mutex_enter_wait
53 | ;Both tries ( c == 2 || cmpxchg( 1, 2 ) ) failed
54 | mutex_enter_cont:
55 | mov rax, 0
56 | mov rdi, 2
57 | lock cmpxchg [queue_futex], rdi
58 | cmp rax, 0
59 | jne mutex_enter_begin
60 | jmp mutex_enter_end
61 |
62 | mutex_enter_wait:
63 | mov rdi, 2
64 | call sys_futex_wait
65 | jmp mutex_enter_cont
66 |
67 | mutex_enter_end:
68 | stackpop
69 | ret
70 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/syscall.asm:
--------------------------------------------------------------------------------
1 | ;asmttpd - Web server for Linux written in amd64 assembly.
2 | ;Copyright (C) 2014 nemasu
3 | ;
4 | ;This file is part of asmttpd.
5 | ;
6 | ;asmttpd is free software: you can redistribute it and/or modify
7 | ;it under the terms of the GNU General Public License as published by
8 | ;the Free Software Foundation, either version 2 of the License, or
9 | ;(at your option) any later version.
10 | ;
11 | ;asmttpd is distributed in the hope that it will be useful,
12 | ;but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | ;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | ;GNU General Public License for more details.
15 | ;
16 | ;You should have received a copy of the GNU General Public License
17 | ;along with asmttpd. If not, see .
18 |
19 | section .text
20 |
21 | sys_cork:;rdi - socket
22 | stackpush
23 | mov r10, one_constant ;pointer to 1
24 | mov r8, 8 ;sizeof int
25 | mov rsi, LEVEL_IPPROTO_TCP
26 | mov rdx, SOCKOPT_TCP_CORK
27 | mov rax, SYS_SETSOCKOPT
28 | syscall
29 | stackpop
30 | ret
31 |
32 | sys_uncork;rdi - socket
33 | stackpush
34 | mov r10, one_constant ;pointer to 1
35 | mov r8, 8 ;sizeof int
36 | mov rsi, LEVEL_IPPROTO_TCP
37 | mov rdx, SOCKOPT_TCP_CORK
38 | mov rax, SYS_SETSOCKOPT
39 | syscall
40 | stackpop
41 | ret
42 |
43 | sys_reuse;rdi - socket
44 | stackpush
45 | mov r8, 8 ;sizeof int
46 | mov r10, one_constant ;pointer to 1
47 | mov rsi, LEVEL_SOL_TCP
48 | mov rdx, SOCKOPT_TCP_REUSEADDR
49 | mov rax, SYS_SETSOCKOPT
50 | syscall
51 | stackpop
52 | ret
53 |
54 | sys_sendfile: ;rdi - outfd, rsi - infd, rdx - file size
55 | stackpush
56 | mov r10, rdx
57 | xor rdx, rdx
58 | mov rax, SYS_SENDFILE
59 | syscall
60 | stackpop
61 | ret
62 |
63 | sys_lseek:; rdi - fd, rsi - offset, rdx - flag
64 | stackpush
65 | mov rax, SYS_LSEEK
66 | syscall
67 | stackpop
68 | ret
69 |
70 | sys_open:
71 | stackpush
72 | mov rsi, OPEN_RDONLY ;flags
73 | mov rax, SYS_OPEN
74 | syscall
75 | stackpop
76 | ret
77 |
78 | sys_close:
79 | stackpush
80 | mov rax, SYS_CLOSE
81 | syscall
82 | stackpop
83 | ret
84 |
85 | sys_send:
86 | stackpush
87 | xor r10, r10
88 | xor r8, r8
89 | xor r9, r9
90 | mov rax, SYS_SENDTO
91 | syscall
92 | stackpop
93 | ret
94 |
95 | sys_recv:
96 | stackpush
97 | xor r10, r10 ; flags
98 | xor r8, r8
99 | xor r9, r9
100 | mov rax, SYS_RECVFROM
101 | syscall
102 | stackpop
103 | ret
104 |
105 | sys_accept:
106 | stackpush
107 | mov rdi, [listen_socket]
108 | xor rsi, rsi
109 | xor rdx, rdx
110 | mov rax, SYS_ACCEPT
111 | syscall
112 | stackpop
113 | ret
114 |
115 | sys_listen:
116 | stackpush
117 | mov rdi, [listen_socket]
118 | mov rsi, 100000000;backlog
119 | mov rax, SYS_LISTEN
120 | syscall
121 | stackpop
122 | ret
123 |
124 | sys_bind_server:
125 | stackpush
126 |
127 | mov rsi, [listen_port]
128 | mov [sa + sin_port], rsi
129 |
130 | mov rdi, [listen_socket]
131 | mov rsi, sa
132 | mov rdx, 16
133 | mov rax, SYS_BIND
134 | syscall
135 | stackpop
136 | ret
137 |
138 | sys_create_tcp_socket:
139 | stackpush
140 | mov rdi, AF_INET
141 | mov rsi, SOCK_STREAM
142 | mov rdx, PROTO_TCP
143 | mov rax, SYS_SOCKET
144 | syscall
145 | stackpop
146 | ret
147 |
148 | sys_open_directory:;rdi = path, rax = ret ( fd )
149 | stackpush
150 | mov rsi, OPEN_DIRECTORY | OPEN_RDONLY ;flags
151 | mov rax, SYS_OPEN
152 | syscall
153 | stackpop
154 | ret
155 |
156 | sys_write:
157 | stackpush
158 | mov rdx, rsi ;length
159 | mov rsi, rdi ;buffer
160 | mov rdi, FD_STDOUT
161 | mov rax, SYS_WRITE
162 | syscall
163 | stackpop
164 | ret
165 |
166 | sys_nanosleep:
167 | stackpush
168 | mov qword [tv_usec], rdi
169 | mov qword [tv_sec],0
170 | xor rsi, rsi
171 | mov rdi, timeval
172 | mov rax, SYS_NANOSLEEP
173 | syscall
174 | stackpop
175 | ret
176 |
177 | sys_sleep:
178 | stackpush
179 | mov qword [tv_sec], rdi
180 | mov qword [tv_usec],0
181 | xor rsi, rsi
182 | mov rdi, timeval
183 | mov rax, SYS_NANOSLEEP
184 | syscall
185 | stackpop
186 | ret
187 |
188 | sys_mmap_mem:
189 | stackpush
190 | mov rsi, rdi ;Size
191 | xor rdi, rdi ;Preferred address (don't care)
192 | mov rdx, MMAP_PROT_READ | MMAP_PROT_WRITE ;Protection Flags
193 | mov r10, MMAP_MAP_PRIVATE | MMAP_MAP_ANON ;Flags
194 | xor r8, r8
195 | dec r8 ;-1 fd because of MMAP_MAP_ANON
196 | xor r9, r9 ;Offset
197 | mov rax, SYS_MMAP
198 | syscall
199 | stackpop
200 | ret
201 |
202 | sys_mmap_stack:
203 | stackpush
204 | mov rsi, rdi ;Size
205 | xor rdi, rdi ;Preferred address (don't care)
206 | mov rdx, MMAP_PROT_READ | MMAP_PROT_WRITE ;Protection Flags
207 | mov r10, MMAP_MAP_PRIVATE | MMAP_MAP_ANON | MMAP_MAP_GROWSDOWN ;Flags
208 | xor r8, r8
209 | dec r8 ;-1 fd because of MMAP_MAP_ANON
210 | xor r9, r9 ;Offset
211 | mov rax, SYS_MMAP
212 | syscall
213 | stackpop
214 | ret
215 |
216 | sys_clone:
217 | mov r14, rdi ;Address of the thread_func
218 | mov r15, rsi ;Thread Param
219 | mov rdi, THREAD_STACK_SIZE
220 | call sys_mmap_stack
221 | lea rsi, [rax + THREAD_STACK_SIZE] ;Set newly allocated memory
222 | mov rdi, CLONE_FILES | CLONE_VM | CLONE_FS | CLONE_THREAD | CLONE_SIGHAND | SIGCHILD ;Flags
223 | xor r10, r10 ;parent_tid
224 | xor r8, r8 ;child_tid
225 | xor r9, r9 ;regs
226 | mov rax, SYS_CLONE
227 | syscall
228 | cmp rax, 0 ;If ret is not 0, then it's the ID of the new thread
229 | jnz parent
230 | push r14 ;Else, set new return address
231 | mov rdi, r15 ;and set param
232 | ret ;Return to thread_proc
233 | parent:
234 | ret
235 |
236 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/asm/web_root/index.html:
--------------------------------------------------------------------------------
1 | hi hello world from assembly
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/go/Dockerfile:
--------------------------------------------------------------------------------
1 | ### First Stage ###
2 | FROM golang:1.14 as builder
3 | COPY . .
4 | RUN go build -ldflags \
5 | "-linkmode external -extldflags -static" \
6 | -a server.go
7 |
8 | ### Second Stage ###
9 | FROM scratch
10 | COPY --from=builder /go/server ./server
11 | COPY index.html index.html
12 | CMD ["./server"]
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/go/Dockerfile.alpine:
--------------------------------------------------------------------------------
1 | FROM golang:1.14-alpine
2 | COPY . .
3 | RUN go build -o server .
4 | CMD ["./server"]
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/go/Dockerfile.alpine-multi:
--------------------------------------------------------------------------------
1 | ### First Stage ###
2 | FROM golang:1.14-alpine AS builder
3 | COPY . .
4 | RUN go build -o server .
5 |
6 | ### Second Stage ###
7 | FROM alpine:3.12
8 | COPY --from=builder /go/server ./server
9 | COPY index.html index.html
10 | CMD ["./server"]
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/go/Dockerfile.naive:
--------------------------------------------------------------------------------
1 | FROM golang:1.14
2 | COPY . .
3 | RUN go build -o server .
4 | CMD ["./server"]
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/go/Dockerfile.scratch:
--------------------------------------------------------------------------------
1 | ### First Stage ###
2 | FROM golang:1.14 as builder
3 | COPY . .
4 | RUN go build -ldflags "-linkmode external -extldflags -static" -a server.go
5 |
6 | ### Second Stage ###
7 | FROM scratch
8 | COPY --from=builder /go/server ./server
9 | COPY index.html index.html
10 | CMD ["./server"]
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/go/index.html:
--------------------------------------------------------------------------------
1 | hello from golang!
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/go/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | )
8 |
9 | func main() {
10 | fileServer := http.FileServer(http.Dir("./"))
11 | http.Handle("/", fileServer)
12 |
13 | fmt.Printf("Starting server at port 8080\n")
14 | if err := http.ListenAndServe(":8080", nil); err != nil {
15 | log.Fatal(err)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/node/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14-alpine
2 | COPY . .
3 | CMD ["node", "index.js"]
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/node/Dockerfile.alpine:
--------------------------------------------------------------------------------
1 | FROM node:14-alpine
2 | COPY index.* ./
3 | CMD ["node", "index.js"]
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/node/Dockerfile.naive:
--------------------------------------------------------------------------------
1 | FROM node:14
2 | COPY . .
3 | CMD ["node", "index.js"]
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/node/Dockerfile.slim:
--------------------------------------------------------------------------------
1 | FROM node:14-slim
2 | COPY index.* ./
3 | CMD ["node", "index.js"]
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/node/index.html:
--------------------------------------------------------------------------------
1 | Hello from node.js! 👋
--------------------------------------------------------------------------------
/2021-04-03-smallest-possible-container/node/index.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const http = require('http');
3 |
4 | const server = http.createServer((req, res) => {
5 | res.writeHead(200, { 'content-type': 'text/html' })
6 | fs.createReadStream('index.html').pipe(res)
7 | })
8 |
9 | server.listen(port, hostname, () => {
10 | console.log(`Server: http://0.0.0.0:8080/`);
11 | });
12 |
--------------------------------------------------------------------------------
/2021-04-19-10000-pods/Makefile:
--------------------------------------------------------------------------------
1 | PROJECT_ID:=devops-directive-gcs-https
2 | KOPS_FEATURE_FLAGS:=AlphaAllowGCE # to unlock the GCE features
3 | STATE_BUCKET:=gs://sid-kops-cluster-state
4 |
5 | CLUSTER_NAME=simple.k8s.local
6 | ZONE:=us-central1-a
7 |
8 | create-state-store:
9 | gsutil mb -p $(PROJECT_ID) $(STATE_BUCKET)
10 |
11 | create-cluster:
12 | KOPS_FEATURE_FLAGS=AlphaAllowGCE \
13 | kops create cluster $(CLUSTER_NAME) \
14 | --zones $(ZONE) \
15 | --state $(STATE_BUCKET)/ \
16 | --project=$(PROJECT_ID) \
17 | --node-count 44 \
18 | --node-size n1-standard-1 \
19 | --master-size n1-standard-4
20 |
21 | update:
22 | KOPS_FEATURE_FLAGS=AlphaAllowGCE \
23 | kops update cluster $(CLUSTER_NAME) \
24 | --state $(STATE_BUCKET)/
25 |
26 | apply-update:
27 | KOPS_FEATURE_FLAGS=AlphaAllowGCE \
28 | kops update cluster $(CLUSTER_NAME) \
29 | --state $(STATE_BUCKET)/ \
30 | --yes
31 |
32 | rolling-update:
33 | KOPS_FEATURE_FLAGS=AlphaAllowGCE \
34 | kops rolling-update cluster $(CLUSTER_NAME) \
35 | --state $(STATE_BUCKET)/ \
36 | --yes
37 |
38 |
39 | validate-cluster:
40 | kops validate cluster --wait 10m --state $(STATE_BUCKET)
41 |
42 | delete-cluster:
43 | kops delete cluster $(CLUSTER_NAME) \
44 | --state $(STATE_BUCKET)/ \
45 | --yes
46 |
47 | edit-cluster:
48 | kops edit cluster $(CLUSTER_NAME) \
49 | --state $(STATE_BUCKET)/
50 |
51 | edit-node-instance-template:
52 | kops edit ig --name=$(CLUSTER_NAME) nodes \
53 | --state $(STATE_BUCKET)/
54 |
55 | edit-master-instance-template:
56 | kops edit ig --name=$(CLUSTER_NAME) master-us-central1-a \
57 | --state $(STATE_BUCKET)/
58 |
59 | create-kubecfg:
60 | kops export kubecfg $(CLUSTER_NAME) \
61 | --state $(STATE_BUCKET)
62 |
--------------------------------------------------------------------------------
/2021-04-19-10000-pods/README.md:
--------------------------------------------------------------------------------
1 | # Attempting Jeff Geerling's 10,000 pod challenge (10k Subscriber Celebration! 🎉)
2 |
3 | Video: https://youtu.be/1y2nRNexRVk
4 |
5 | I used my tiny ASM based webserver container from the previous video (and a few other tricks) to launch over 11k pods into a cluster with just 44 x 1vCPU worker nodes!
--------------------------------------------------------------------------------
/2021-04-19-10000-pods/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: asmttpd
5 | namespace: all-the-pods
6 | spec:
7 | replicas: 10001
8 | selector:
9 | matchLabels:
10 | app: asmttpd
11 | template:
12 | metadata:
13 | labels:
14 | app: asmttpd
15 | spec:
16 | containers:
17 | - image: sidpalas/asmttpd:0.0.1
18 | imagePullPolicy: IfNotPresent
19 | name: asmttpd
20 | resources:
21 | limits:
22 | cpu: 3m
23 | memory: 15Mi
24 | requests:
25 | cpu: 2m
26 | memory: 10Mi
27 |
--------------------------------------------------------------------------------
/2021-04-19-10000-pods/visualizations/.gitignore:
--------------------------------------------------------------------------------
1 | *.txt
2 | *.csv
--------------------------------------------------------------------------------
/2021-04-19-10000-pods/visualizations/Makefile:
--------------------------------------------------------------------------------
1 | plot:
2 | poetry run python plot.py
3 |
4 | # ---
5 |
6 | BASE_NAMESPACE?=all-the-pods
7 |
8 | HEADER:="timestamp, pending-pods, creating-pods, running-pods"
9 |
10 | create-csv:
11 | echo $(HEADER) > progress.csv
12 |
13 | extract-progress:
14 | @printf `date +'%s', ` >> progress.csv
15 | @cat pods.txt | grep Pending | wc -l | tr -d '\n'>> progress.csv
16 | @printf ", " >> progress.csv
17 | @cat pods.txt | grep ContainerC | wc -l | tr -d '\n'>> progress.csv
18 | @printf ", " >> progress.csv
19 | @cat pods.txt | grep Running | wc -l >> progress.csv
20 |
21 | echo-progress:
22 | @tail -1 progress.csv
23 |
24 | kubectl-to-file:
25 | @kubectl get $(RESOURCE) -n $(BASE_NAMESPACE) > $(RESOURCE).txt
26 |
27 | track-progress:
28 | while [[ true ]] ; do \
29 | $(MAKE) RESOURCE=pods kubectl-to-file ; \
30 | $(MAKE) extract-progress ; \
31 | $(MAKE) echo-progress ; \
32 | sleep 10 ; \
33 | done
--------------------------------------------------------------------------------
/2021-04-19-10000-pods/visualizations/plot.py:
--------------------------------------------------------------------------------
1 | import matplotlib.pyplot as plt
2 | import matplotlib.animation as animation
3 | import pandas as pd
4 | import time
5 |
6 | NUM_NAMESPACES = 1
7 | TOTAL_GOAL_PODS = 10001
8 | GOAL_PODS = TOTAL_GOAL_PODS/NUM_NAMESPACES
9 |
10 |
11 | fig = plt.figure()
12 | ax1 = fig.add_subplot(2,1,1)
13 | ax2 = fig.add_subplot(2,1,2)
14 | time_elapsed_text = ax2.text(0.1, 0.25, '', fontsize=15)
15 | running_pods_text = ax2.text(0.1, 0.5, '', fontsize=15)
16 | completion_estimate_text = ax2.text(0.1, 0.75, '', fontsize=15)
17 |
18 | def animate(i):
19 | df = pd.read_csv('./progress.csv')
20 | df.columns = df.columns.str.replace(' ', '')
21 | df['relative-timestamp'] = df['timestamp'] - df['timestamp'].min()
22 | df.drop('timestamp', axis='columns', inplace=True)
23 | df.set_index('relative-timestamp', inplace=True)
24 | ax1.clear()
25 | ax1.plot(df)
26 | ax1.legend(df.columns, loc='upper left')
27 | ax1.set_title(f'Progress Towards 10k!')
28 | ax1.set_xlabel('Time Elapsed (s)')
29 | ax1.set_ylabel('Count')
30 |
31 |
32 | # Calculate estimated time
33 | minutes_elapsed = df.last_valid_index()/60
34 |
35 | current_running_pods = df['running-pods'].max()
36 | portion_complete = current_running_pods/GOAL_PODS
37 |
38 | estimated_completion_minutes = minutes_elapsed / portion_complete
39 |
40 | time_elapsed_str = f'Minutes elapsed: {"{0:.2f}".format(minutes_elapsed)} minutes'
41 | time_elapsed_text.set_text(time_elapsed_str)
42 |
43 | running_pods_str = f'Number of running pods: {current_running_pods}'
44 | running_pods_text.set_text(running_pods_str)
45 |
46 | completion_estimate_str = f'Estimated completion time: {"{0:.2f}".format(estimated_completion_minutes)} minutes'
47 | completion_estimate_text.set_text(completion_estimate_str)
48 |
49 | ani = animation.FuncAnimation(fig, animate, interval=2000)
50 | plt.show()
--------------------------------------------------------------------------------
/2021-04-19-10000-pods/visualizations/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | authors = ["Your Name "]
3 | description = ""
4 | name = "visualizations"
5 | version = "0.1.0"
6 |
7 | [tool.poetry.dependencies]
8 | matplotlib = "^3.3.2"
9 | pandas = "^1.1.3"
10 | python = "^3.7"
11 |
12 | [tool.poetry.dev-dependencies]
13 |
14 | [build-system]
15 | build-backend = "poetry.masonry.api"
16 | requires = ["poetry>=0.12"]
17 |
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/README.md:
--------------------------------------------------------------------------------
1 | # Developing with Docker
2 |
3 | Video: https://youtu.be/5JQlFK6MdVQ
4 |
5 | ## Developer Experience issues:
6 |
7 | ### Slow iteration:
8 |
9 | Rebuilding the image is slow. How can we use something like nodemon to watch for file changes?
10 |
11 | Yes, but we must use volume mounts such that changes to the host filesystem are reflected inside the container:
12 |
13 | - Install nodemon globally in Dockerfile (`RUN yarn global add nodemon`)
14 | - Mount the source code into the container at runtime (`-v $(PWD):/usr/src/app`)
15 | - Use an empty mount to avoid mounting the node_modules directory (`-v /usr/src/app/node_modules`
16 | - Override cmd at runtime (`docker run... nodemon server.js`)
17 |
18 | ### Debugging:
19 |
20 | Do normal debugging tools work?
21 |
22 | Yes, but we must forward the necessary port and have the debugger listen on `0.0.0.0`:
23 |
24 | - Use the inspect flag at runtime with explicit address (`--inspect=0.0.0.0:9229`)
25 | - Add port forward for the proper port (`-p 9229:9229`)
26 | - Connect to debugger like normal (one option `chrome://about:inspect`)
27 |
28 | ### Lots of command line flags + complexity
29 |
30 | Now the docker run commands are super complex. How can we deal with that?
31 |
32 | Use docker compose to encode the configuration in a yaml file!
33 |
34 |
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/improved/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/improved/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14
2 |
3 | # Create app directory
4 | WORKDIR /usr/src/app
5 |
6 | # Install nodemon
7 | RUN yarn global add nodemon
8 |
9 | # Install app dependencies
10 | COPY package.json ./
11 | COPY yarn.lock ./
12 | RUN yarn
13 |
14 | # Copy in the source
15 | COPY . .
16 |
17 | # Don't use root user
18 | USER node
19 |
20 | # Expose Port
21 | EXPOSE 3000
22 |
23 | # Run the app
24 | CMD ["node", "server.js"]
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/improved/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | docker build -t express-hello-world .
3 |
4 | run:
5 | docker run --rm -p 3000:3000 express-hello-world
6 |
7 | run-dev:
8 | docker run \
9 | -p 3000:3000 \
10 | -p 9229:9229 \
11 | -v $(PWD):/usr/src/app \
12 | -v /usr/src/app/node_modules \
13 | hello-world-express nodemon --inspect=0.0.0.0:9229 server.js
14 |
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/improved/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 | services:
3 | express:
4 | build:
5 | context: ./
6 | ports:
7 | - '3000:3000'
8 | - '9229:9229'
9 | volumes:
10 | - .:/usr/src/app
11 | - /usr/src/app/node_modules
12 | environment:
13 | - NODE_ENV=development
14 | - DEBUG=express:*
15 | command:
16 | - nodemon
17 | - --inspect=0.0.0.0:9229
18 | - server.js
19 | # databaseservice
20 | # ...
21 | # front-end service
22 | # ...
23 |
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/improved/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "2021-08-16-improved",
3 | "version": "1.0.0",
4 | "main": "server.js",
5 | "license": "MIT",
6 | "dependencies": {
7 | "express": "^4.17.1"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/improved/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const app = express()
3 | const port = 3000
4 |
5 | app.get('/', (req, res) => {
6 | debugger
7 | res.send('Hello World!')
8 | })
9 |
10 | app.listen(port, () => {
11 | console.log(`Example app listening at http://localhost:${port}`)
12 | })
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/naive/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/naive/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14
2 |
3 | # Create app directory
4 | WORKDIR /usr/src/app
5 |
6 | # Install app dependencies
7 | COPY package.json ./
8 | COPY yarn.lock ./
9 | RUN yarn
10 |
11 | # Copy in the source code
12 | COPY . .
13 |
14 | # Don't use root user
15 | USER node
16 |
17 | # Expose Port
18 | EXPOSE 3000
19 |
20 | # Run the app
21 | CMD ["node", "server.js"]
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/naive/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | docker build -t express-hello-world .
3 |
4 | run:
5 | docker run --rm -p 3000:3000 express-hello-world
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/naive/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "2021-08-15-docker-dev-environment",
3 | "version": "1.0.0",
4 | "main": "server.js",
5 | "dependencies": {
6 | "express": "^4.17.1"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/2021-08-15-docker-dev-environment/naive/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const app = express()
3 | const port = 3000
4 |
5 | app.get('/', (req, res) => {
6 | res.send('Hello World!')
7 | })
8 |
9 | app.listen(port, () => {
10 | console.log(`Example app listening at http://localhost:${port}`)
11 | })
--------------------------------------------------------------------------------
/2022-08-01-getting-started-with-k8s/.gitignore:
--------------------------------------------------------------------------------
1 | venv
2 | __pycache__
3 |
--------------------------------------------------------------------------------
/2022-08-01-getting-started-with-k8s/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.9
2 |
3 | WORKDIR /code
4 |
5 | COPY ./requirements.txt /code/requirements.txt
6 |
7 | RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8 |
9 | COPY ./app /code/app
10 |
11 | CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
--------------------------------------------------------------------------------
/2022-08-01-getting-started-with-k8s/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Kubernetes
2 |
3 | Video: https://www.youtube.com/watch?v=XltFOyGanYE
4 |
5 | ## Python Application
6 |
7 | ### Create a virtual environment:
8 | ```bash
9 | python3 -m venv ./venv
10 | source ./venv/bin/activate
11 | ```
12 |
13 | ### Install the Dependencies
14 | ```bash
15 | pip install -r requirements.txt
16 | ```
17 |
18 | ### Start the App
19 | ```bash
20 | uvicorn main:app --reload
21 | ```
22 |
23 | ## Containerize the Application
24 |
25 | ```bash
26 | docker build -t .
27 |
28 | docker push -t
29 | ```
30 |
31 | ## Deploy to Kubernetess:
32 |
33 | ```bash
34 | export KUBECONFIG=
35 |
36 | kubectl apply -f .
37 |
38 | kubectl get pods
39 | ```
40 |
41 | ### Validation and Debugging
42 |
43 | ```bash
44 | kubectl port-forward 8080:80
45 |
46 | kubectl exec -it -- bash
47 | ```
--------------------------------------------------------------------------------
/2022-08-01-getting-started-with-k8s/app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidpalas/devops-directive/fbbd56f8526852da545b1a04ddd2c8a2a68b198a/2022-08-01-getting-started-with-k8s/app/__init__.py
--------------------------------------------------------------------------------
/2022-08-01-getting-started-with-k8s/app/main.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI
2 |
3 | import os
4 |
5 | app = FastAPI()
6 |
7 |
8 | @app.get("/")
9 | def read_root():
10 | return {"Hello": f"From: {os.environ.get('HOSTNAME', 'DEFAULT_ENV')}"}
11 |
--------------------------------------------------------------------------------
/2022-08-01-getting-started-with-k8s/kubernetes/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: fast-api
5 | labels:
6 | app: fast-api
7 | spec:
8 | replicas: 3
9 | selector:
10 | matchLabels:
11 | app: fast-api
12 | template:
13 | metadata:
14 | labels:
15 | app: fast-api
16 | spec:
17 | containers:
18 | - name: fast-api
19 | image: sidpalas/k8s-getting-started:0.0.3
20 | ports:
21 | - containerPort: 80
22 | resources:
23 | requests:
24 | cpu: 200m
25 | memory: 300Mi
26 | limits:
27 | memory: 400Mi
28 | env:
29 | - name: ENV
30 | value: "CIVO"
--------------------------------------------------------------------------------
/2022-08-01-getting-started-with-k8s/kubernetes/ingress.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: fast-api
5 | spec:
6 | rules:
7 | - host: k8s.devopsdirective.com
8 | http:
9 | paths:
10 | - path: /
11 | pathType: Exact
12 | backend:
13 | service:
14 | name: fast-api
15 | port:
16 | number: 80
--------------------------------------------------------------------------------
/2022-08-01-getting-started-with-k8s/kubernetes/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: fast-api
5 | spec:
6 | selector:
7 | app: fast-api
8 | ports:
9 | - protocol: TCP
10 | port: 80
11 | targetPort: 80
--------------------------------------------------------------------------------
/2022-08-01-getting-started-with-k8s/requirements.txt:
--------------------------------------------------------------------------------
1 | fastapi~=0.79
2 | uvicorn~=0.18
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 sidpalas
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # devops-directive
2 | Code samples from the DevOps Directive YouTube channel
3 |
--------------------------------------------------------------------------------