├── .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 | 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 | 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 | 89 | 94 | 95 | 96 | 106 | 107 | 108 | 113 | 114 | 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 | 101 | 106 | 107 | 108 | 118 | 119 | 120 | 125 | 126 | 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 | 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 |
8 |
9 | logo 10 |

Super Simple Static Site Setup w/ SSL (S^6)

11 |

GCS + Cloudflare = 😍🎉

12 | 18 | DevOps Directive Home 19 | 20 |
21 |
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 | [![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](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 | 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 | 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 | 89 | 94 | 95 | 96 | 106 | 107 | 108 | 113 | 114 | 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 | 101 | 106 | 107 | 108 | 118 | 119 | 120 | 125 | 126 | 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 | --------------------------------------------------------------------------------