├── .browserslistrc ├── .github └── workflows │ └── deploy.yaml ├── .gitignore ├── .pryrc ├── .psqlrc ├── Aptfile ├── Dockerfile ├── Dockerfile.prod ├── Gemfile ├── Gemfile.lock ├── Makefile ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .keep │ │ ├── do-logo.png │ │ └── wagon-logo.png │ └── stylesheets │ │ └── application.css ├── channels │ ├── application_cable │ │ ├── channel.rb │ │ └── connection.rb │ └── messages_channel.rb ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ ├── logins_controller.rb │ └── messages_controller.rb ├── helpers │ ├── application_helper.rb │ ├── login_helper.rb │ └── messages_helper.rb ├── javascript │ ├── channels │ │ ├── consumer.js │ │ └── index.js │ ├── controllers │ │ ├── chat_controller.js │ │ └── index.js │ ├── packs │ │ └── application.js │ └── stylesheets │ │ ├── application.scss │ │ └── chat.scss ├── jobs │ ├── application_job.rb │ └── deliver_message_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── current.rb │ ├── message.rb │ └── preview.rb ├── services │ ├── message_broadcaster.rb │ ├── preview_broadcaster.rb │ └── preview_creator.rb └── views │ ├── layouts │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ ├── logins │ └── new.html.erb │ ├── messages │ ├── _message.html.erb │ └── index.html.erb │ └── previews │ └── _preview.html.erb ├── babel.config.js ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring ├── webpack ├── webpack-dev-server └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── sidekiq_redis.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── sidekiq.yml ├── spring.rb ├── webpack │ ├── development.js │ ├── environment.js │ ├── production.js │ └── test.js └── webpacker.yml ├── db ├── migrate │ ├── 20191126104811_create_messages.rb │ └── 20191203142803_create_previews.rb ├── schema.rb └── seeds.rb ├── dip.yml ├── docker-compose.yml ├── docs ├── app.png ├── processes.png └── secrets.png ├── helm ├── Chart.lock ├── Chart.yaml ├── charts │ └── redis-10.2.1.tgz ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── deployments │ │ ├── rails.yaml │ │ └── sidekiq.yaml │ ├── environment │ │ └── common-env.yaml │ ├── ingress.yaml │ ├── jobs │ │ └── db-prepare.yaml │ ├── secrets │ │ ├── db-connection-secret.yaml │ │ └── master-key-secret.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ ├── ssl │ │ ├── cert.yaml │ │ └── production-issuer.yaml │ └── tests │ │ └── test-connection.yaml └── values.yaml ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package.json ├── postcss.config.js ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── test ├── application_system_test_case.rb ├── channels │ ├── application_cable │ │ └── connection_test.rb │ └── messages_channel_test.rb ├── controllers │ ├── .keep │ ├── logins_controller_test.rb │ └── messages_controller_test.rb ├── fixtures │ ├── .keep │ └── files │ │ └── .keep ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── message_test.rb │ └── preview_test.rb ├── system │ └── .keep └── test_helper.rb ├── tmp └── .keep ├── vendor └── .keep └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Helm deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@master 12 | 13 | - name: Login to Quay 14 | run: docker login quay.io -u ${{ secrets.QUAY_USERNAME }} -p "${{ secrets.QUAY_PASSWORD }}" 15 | 16 | - name: Pull latest image to use for cache 17 | run: docker pull quay.io/lewagon/rails-k8s-demo:latest || true 18 | 19 | - name: Copy master key from a secret 20 | run: echo ${{ secrets.RAILS_MASTER_KEY }} > config/master.key 21 | 22 | - name: Build and push prod image 23 | run: make build-ci 24 | 25 | - name: Install doctl 26 | uses: digitalocean/action-doctl@v2 27 | with: 28 | token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} 29 | 30 | - name: Save DigitalOcean kubeconfig 31 | run: doctl kubernetes cluster kubeconfig show kubernetes-demo > $GITHUB_WORKSPACE/.kubeconfig 32 | 33 | - name: Install/upgrade chart 34 | run: > 35 | export KUBECONFIG=$GITHUB_WORKSPACE/.kubeconfig && 36 | make ci-deploy latest_sha=$(echo $GITHUB_SHA | head -c7) 37 | db_connection_string=${{ secrets.DB_URL }} rails_master_key=${{secrets.RAILS_MASTER_KEY}} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | 17 | /public/assets 18 | .byebug_history 19 | 20 | # Ignore master key for decrypting credentials and more. 21 | /config/master.key 22 | 23 | /public/packs 24 | /public/packs-test 25 | /node_modules 26 | /yarn-error.log 27 | yarn-debug.log* 28 | .yarn-integrity 29 | .pry_history 30 | .ruby-version 31 | -------------------------------------------------------------------------------- /.pryrc: -------------------------------------------------------------------------------- 1 | Pry.config.history.should_save = true 2 | Pry.config.history.file = File.join(__dir__, ".pry_history") 3 | -------------------------------------------------------------------------------- /.psqlrc: -------------------------------------------------------------------------------- 1 | \set HISTFILE `[[ -z $PSQL_HISTFILE ]] && echo $HOME/.psql_history || echo $PSQL_HISTFILE` 2 | -------------------------------------------------------------------------------- /Aptfile: -------------------------------------------------------------------------------- 1 | vim 2 | libidn11-dev 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # https://github.com/lewagon/rails-base-chrome-imagemagick/tree/dev 2 | FROM quay.io/lewagon/rails-base-chrome-imagemagick:dev 3 | ARG BUNDLER_VERSION 4 | ENV BUNDLER_VERSION=${BUNDLER_VERSION:-2.1.4} 5 | 6 | COPY ./Aptfile /tmp/Aptfile 7 | RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \ 8 | DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ 9 | $(cat /tmp/Aptfile | xargs) && \ 10 | apt-get clean && \ 11 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ 12 | truncate -s 0 /var/log/*log 13 | 14 | # Configure bundler 15 | ENV LANG=C.UTF-8 \ 16 | BUNDLE_JOBS=4 \ 17 | BUNDLE_RETRY=3 18 | 19 | ENV PATH /app/bin:$PATH 20 | 21 | WORKDIR /app 22 | 23 | # Upgrade RubyGems and install required Bundler version 24 | RUN gem update --system && \ 25 | gem install bundler -v $BUNDLER_VERSION -------------------------------------------------------------------------------- /Dockerfile.prod: -------------------------------------------------------------------------------- 1 | # https://github.com/lewagon/rails-base-chrome-imagemagick/tree/dev 2 | FROM quay.io/lewagon/rails-base-chrome-imagemagick:dev 3 | ARG BUNDLER_VERSION 4 | ENV BUNDLER_VERSION=${BUNDLER_VERSION:-2.1.4} 5 | 6 | COPY ./Aptfile /tmp/Aptfile 7 | RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \ 8 | DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ 9 | $(cat /tmp/Aptfile | xargs) && \ 10 | apt-get clean && \ 11 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ 12 | truncate -s 0 /var/log/*log 13 | 14 | # Configure bundler 15 | ENV LANG=C.UTF-8 \ 16 | BUNDLE_JOBS=4 \ 17 | BUNDLE_RETRY=3 18 | 19 | ENV PATH /app/bin:$PATH 20 | 21 | WORKDIR /app 22 | 23 | COPY Gemfile Gemfile.lock package.json yarn.lock ./ 24 | 25 | # Upgrade RubyGems and install required Bundler version 26 | RUN gem update --system && \ 27 | gem install bundler:$BUNDLER_VERSION && \ 28 | bundle config set deployment 'true' && \ 29 | bundle config set without 'development test' && \ 30 | bundle install 31 | 32 | COPY app/ ./app 33 | COPY bin/ ./bin 34 | COPY config/ ./config 35 | COPY lib/ ./lib 36 | COPY db/ ./db 37 | COPY public/ ./public 38 | COPY config.ru Rakefile postcss.config.js babel.config.js ./ 39 | RUN mkdir log 40 | 41 | 42 | ENV RAILS_ENV=production 43 | ENV NODE_ENV=production 44 | 45 | # Hack to not leak the master_key for build 46 | # https://github.com/rails/rails/issues/32947#issuecomment-531508722 47 | ENV ASSETS_PRECOMPILE=1 48 | ENV SECRET_KEY_BASE=1 49 | 50 | RUN rails assets:precompile 51 | 52 | ENV RAILS_LOG_TO_STDOUT=enabled 53 | ENV RAILS_SERVE_STATIC_FILES=enabled 54 | 55 | # cleanup 56 | RUN rm -rf node_modules tmp/cache vendor/assets test 57 | 58 | EXPOSE 3000 -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby "2.6.6" 5 | 6 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 7 | gem "rails", "~> 6.0.3.1" 8 | # Use postgresql as the database for Active Record 9 | gem "pg", ">= 0.18", "< 2.0" 10 | # Use Puma as the app server 11 | gem "puma", "~> 3.12" 12 | # Use SCSS for stylesheets 13 | gem "sass-rails", "~> 6" 14 | # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker 15 | gem "webpacker", "~> 4.0" 16 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 17 | gem "turbolinks", "~> 5" 18 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 19 | gem "jbuilder", "~> 2.7" 20 | # Use Redis adapter to run Action Cable in production 21 | gem "redis", "~> 4.0" 22 | gem "sidekiq", "~> 6.0" 23 | gem "sidekiq-failures" 24 | gem "sidekiq_alive", "~> 2.0" # liveness probe for Sidekiq pod in Kubernetes 25 | 26 | gem "http" 27 | gem "twitter-text" 28 | gem "opengraph_parser" 29 | # Use Active Model has_secure_password 30 | # gem 'bcrypt', '~> 3.1.7' 31 | 32 | # Reduces boot times through caching; required in config/boot.rb 33 | gem "bootsnap", ">= 1.4.2", require: false 34 | 35 | group :development, :test do 36 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 37 | gem "byebug", platforms: [:mri, :mingw, :x64_mingw] 38 | end 39 | 40 | group :development do 41 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 42 | gem "web-console", ">= 3.3.0" 43 | gem "listen", ">= 3.0.5", "< 3.2" 44 | gem "httplog" 45 | gem "rspec", "~> 3.9" 46 | gem "pry-rails" 47 | end 48 | 49 | group :test do 50 | # Adds support for Capybara system testing and selenium driver 51 | gem "capybara", ">= 2.15" 52 | gem "selenium-webdriver" 53 | # Easy installation and use of web drivers to run system tests with browsers 54 | gem "webdrivers" 55 | end 56 | 57 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 58 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] 59 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (6.0.3.2) 5 | actionpack (= 6.0.3.2) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailbox (6.0.3.2) 9 | actionpack (= 6.0.3.2) 10 | activejob (= 6.0.3.2) 11 | activerecord (= 6.0.3.2) 12 | activestorage (= 6.0.3.2) 13 | activesupport (= 6.0.3.2) 14 | mail (>= 2.7.1) 15 | actionmailer (6.0.3.2) 16 | actionpack (= 6.0.3.2) 17 | actionview (= 6.0.3.2) 18 | activejob (= 6.0.3.2) 19 | mail (~> 2.5, >= 2.5.4) 20 | rails-dom-testing (~> 2.0) 21 | actionpack (6.0.3.2) 22 | actionview (= 6.0.3.2) 23 | activesupport (= 6.0.3.2) 24 | rack (~> 2.0, >= 2.0.8) 25 | rack-test (>= 0.6.3) 26 | rails-dom-testing (~> 2.0) 27 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 28 | actiontext (6.0.3.2) 29 | actionpack (= 6.0.3.2) 30 | activerecord (= 6.0.3.2) 31 | activestorage (= 6.0.3.2) 32 | activesupport (= 6.0.3.2) 33 | nokogiri (>= 1.8.5) 34 | actionview (6.0.3.2) 35 | activesupport (= 6.0.3.2) 36 | builder (~> 3.1) 37 | erubi (~> 1.4) 38 | rails-dom-testing (~> 2.0) 39 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 40 | activejob (6.0.3.2) 41 | activesupport (= 6.0.3.2) 42 | globalid (>= 0.3.6) 43 | activemodel (6.0.3.2) 44 | activesupport (= 6.0.3.2) 45 | activerecord (6.0.3.2) 46 | activemodel (= 6.0.3.2) 47 | activesupport (= 6.0.3.2) 48 | activestorage (6.0.3.2) 49 | actionpack (= 6.0.3.2) 50 | activejob (= 6.0.3.2) 51 | activerecord (= 6.0.3.2) 52 | marcel (~> 0.3.1) 53 | activesupport (6.0.3.2) 54 | concurrent-ruby (~> 1.0, >= 1.0.2) 55 | i18n (>= 0.7, < 2) 56 | minitest (~> 5.1) 57 | tzinfo (~> 1.1) 58 | zeitwerk (~> 2.2, >= 2.2.2) 59 | addressable (2.7.0) 60 | public_suffix (>= 2.0.2, < 5.0) 61 | bindex (0.8.1) 62 | bootsnap (1.4.6) 63 | msgpack (~> 1.0) 64 | builder (3.2.4) 65 | byebug (11.1.3) 66 | capybara (3.32.2) 67 | addressable 68 | mini_mime (>= 0.1.3) 69 | nokogiri (~> 1.8) 70 | rack (>= 1.6.0) 71 | rack-test (>= 0.6.3) 72 | regexp_parser (~> 1.5) 73 | xpath (~> 3.2) 74 | childprocess (3.0.0) 75 | coderay (1.1.3) 76 | concurrent-ruby (1.1.6) 77 | connection_pool (2.2.3) 78 | crass (1.0.6) 79 | diff-lcs (1.3) 80 | domain_name (0.5.20190701) 81 | unf (>= 0.0.5, < 1.0.0) 82 | erubi (1.9.0) 83 | ffi (1.13.1) 84 | ffi-compiler (1.0.1) 85 | ffi (>= 1.0.0) 86 | rake 87 | globalid (0.4.2) 88 | activesupport (>= 4.2.0) 89 | http (4.4.1) 90 | addressable (~> 2.3) 91 | http-cookie (~> 1.0) 92 | http-form_data (~> 2.2) 93 | http-parser (~> 1.2.0) 94 | http-cookie (1.0.3) 95 | domain_name (~> 0.5) 96 | http-form_data (2.3.0) 97 | http-parser (1.2.1) 98 | ffi-compiler (>= 1.0, < 2.0) 99 | httplog (1.4.3) 100 | rack (>= 1.0) 101 | rainbow (>= 2.0.0) 102 | i18n (1.8.3) 103 | concurrent-ruby (~> 1.0) 104 | idn-ruby (0.1.0) 105 | jbuilder (2.10.0) 106 | activesupport (>= 5.0.0) 107 | listen (3.1.5) 108 | rb-fsevent (~> 0.9, >= 0.9.4) 109 | rb-inotify (~> 0.9, >= 0.9.7) 110 | ruby_dep (~> 1.2) 111 | loofah (2.6.0) 112 | crass (~> 1.0.2) 113 | nokogiri (>= 1.5.9) 114 | mail (2.7.1) 115 | mini_mime (>= 0.1.1) 116 | marcel (0.3.3) 117 | mimemagic (~> 0.3.2) 118 | method_source (1.0.0) 119 | mimemagic (0.3.5) 120 | mini_mime (1.0.2) 121 | mini_portile2 (2.4.0) 122 | minitest (5.14.1) 123 | msgpack (1.3.3) 124 | mustermann (1.1.1) 125 | ruby2_keywords (~> 0.0.1) 126 | nio4r (2.5.2) 127 | nokogiri (1.10.9) 128 | mini_portile2 (~> 2.4.0) 129 | opengraph_parser (0.2.3) 130 | addressable 131 | nokogiri 132 | pg (1.2.3) 133 | pry (0.13.1) 134 | coderay (~> 1.1) 135 | method_source (~> 1.0) 136 | pry-rails (0.3.9) 137 | pry (>= 0.10.4) 138 | public_suffix (4.0.5) 139 | puma (3.12.6) 140 | rack (2.2.3) 141 | rack-protection (2.0.8.1) 142 | rack 143 | rack-proxy (0.6.5) 144 | rack 145 | rack-test (1.1.0) 146 | rack (>= 1.0, < 3) 147 | rails (6.0.3.2) 148 | actioncable (= 6.0.3.2) 149 | actionmailbox (= 6.0.3.2) 150 | actionmailer (= 6.0.3.2) 151 | actionpack (= 6.0.3.2) 152 | actiontext (= 6.0.3.2) 153 | actionview (= 6.0.3.2) 154 | activejob (= 6.0.3.2) 155 | activemodel (= 6.0.3.2) 156 | activerecord (= 6.0.3.2) 157 | activestorage (= 6.0.3.2) 158 | activesupport (= 6.0.3.2) 159 | bundler (>= 1.3.0) 160 | railties (= 6.0.3.2) 161 | sprockets-rails (>= 2.0.0) 162 | rails-dom-testing (2.0.3) 163 | activesupport (>= 4.2.0) 164 | nokogiri (>= 1.6) 165 | rails-html-sanitizer (1.3.0) 166 | loofah (~> 2.3) 167 | railties (6.0.3.2) 168 | actionpack (= 6.0.3.2) 169 | activesupport (= 6.0.3.2) 170 | method_source 171 | rake (>= 0.8.7) 172 | thor (>= 0.20.3, < 2.0) 173 | rainbow (3.0.0) 174 | rake (13.0.1) 175 | rb-fsevent (0.10.4) 176 | rb-inotify (0.10.1) 177 | ffi (~> 1.0) 178 | redis (4.2.1) 179 | regexp_parser (1.7.1) 180 | rspec (3.9.0) 181 | rspec-core (~> 3.9.0) 182 | rspec-expectations (~> 3.9.0) 183 | rspec-mocks (~> 3.9.0) 184 | rspec-core (3.9.2) 185 | rspec-support (~> 3.9.3) 186 | rspec-expectations (3.9.2) 187 | diff-lcs (>= 1.2.0, < 2.0) 188 | rspec-support (~> 3.9.0) 189 | rspec-mocks (3.9.1) 190 | diff-lcs (>= 1.2.0, < 2.0) 191 | rspec-support (~> 3.9.0) 192 | rspec-support (3.9.3) 193 | ruby2_keywords (0.0.2) 194 | ruby_dep (1.5.0) 195 | rubyzip (2.3.0) 196 | sass-rails (6.0.0) 197 | sassc-rails (~> 2.1, >= 2.1.1) 198 | sassc (2.4.0) 199 | ffi (~> 1.9) 200 | sassc-rails (2.1.2) 201 | railties (>= 4.0.0) 202 | sassc (>= 2.0) 203 | sprockets (> 3.0) 204 | sprockets-rails 205 | tilt 206 | selenium-webdriver (3.142.7) 207 | childprocess (>= 0.5, < 4.0) 208 | rubyzip (>= 1.2.2) 209 | sidekiq (6.0.7) 210 | connection_pool (>= 2.2.2) 211 | rack (~> 2.0) 212 | rack-protection (>= 2.0.0) 213 | redis (>= 4.1.0) 214 | sidekiq-failures (1.0.0) 215 | sidekiq (>= 4.0.0) 216 | sidekiq_alive (2.0.2) 217 | sidekiq 218 | sinatra 219 | sinatra (2.0.8.1) 220 | mustermann (~> 1.0) 221 | rack (~> 2.0) 222 | rack-protection (= 2.0.8.1) 223 | tilt (~> 2.0) 224 | sprockets (4.0.2) 225 | concurrent-ruby (~> 1.0) 226 | rack (> 1, < 3) 227 | sprockets-rails (3.2.1) 228 | actionpack (>= 4.0) 229 | activesupport (>= 4.0) 230 | sprockets (>= 3.0.0) 231 | thor (1.0.1) 232 | thread_safe (0.3.6) 233 | tilt (2.0.10) 234 | turbolinks (5.2.1) 235 | turbolinks-source (~> 5.2) 236 | turbolinks-source (5.2.0) 237 | twitter-text (3.1.0) 238 | idn-ruby 239 | unf (~> 0.1.0) 240 | tzinfo (1.2.7) 241 | thread_safe (~> 0.1) 242 | unf (0.1.4) 243 | unf_ext 244 | unf_ext (0.0.7.7) 245 | web-console (4.0.3) 246 | actionview (>= 6.0.0) 247 | activemodel (>= 6.0.0) 248 | bindex (>= 0.4.0) 249 | railties (>= 6.0.0) 250 | webdrivers (4.4.1) 251 | nokogiri (~> 1.6) 252 | rubyzip (>= 1.3.0) 253 | selenium-webdriver (>= 3.0, < 4.0) 254 | webpacker (4.2.2) 255 | activesupport (>= 4.2) 256 | rack-proxy (>= 0.6.1) 257 | railties (>= 4.2) 258 | websocket-driver (0.7.2) 259 | websocket-extensions (>= 0.1.0) 260 | websocket-extensions (0.1.5) 261 | xpath (3.2.0) 262 | nokogiri (~> 1.8) 263 | zeitwerk (2.3.0) 264 | 265 | PLATFORMS 266 | ruby 267 | 268 | DEPENDENCIES 269 | bootsnap (>= 1.4.2) 270 | byebug 271 | capybara (>= 2.15) 272 | http 273 | httplog 274 | jbuilder (~> 2.7) 275 | listen (>= 3.0.5, < 3.2) 276 | opengraph_parser 277 | pg (>= 0.18, < 2.0) 278 | pry-rails 279 | puma (~> 3.12) 280 | rails (~> 6.0.3.1) 281 | redis (~> 4.0) 282 | rspec (~> 3.9) 283 | sass-rails (~> 6) 284 | selenium-webdriver 285 | sidekiq (~> 6.0) 286 | sidekiq-failures 287 | sidekiq_alive (~> 2.0) 288 | turbolinks (~> 5) 289 | twitter-text 290 | tzinfo-data 291 | web-console (>= 3.3.0) 292 | webdrivers 293 | webpacker (~> 4.0) 294 | 295 | RUBY VERSION 296 | ruby 2.6.6p146 297 | 298 | BUNDLED WITH 299 | 2.1.4 300 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | project_name := rails-k8s-demo 2 | image_name := quay.io/lewagon/$(project_name) 3 | latest_sha := $(shell git rev-parse --verify --short HEAD) 4 | rails_master_key := "" 5 | db_connection_string := "" 6 | 7 | ci-deploy: 8 | echo "Upgrading/installing release to specified Digital Ocean cluster" 9 | helm upgrade $(project_name) helm --install \ 10 | --atomic --cleanup-on-fail \ 11 | --set-string image.tag=$(latest_sha) \ 12 | --set-string dbConnectionString=$(db_connection_string) 13 | 14 | build-sha-cached: 15 | set -e; \ 16 | DOCKER_BUILDKIT=1 \ 17 | docker build \ 18 | -f Dockerfile.prod \ 19 | --build-arg RUBY_VERSION="2.6.5" \ 20 | --build-arg PG_MAJOR="11" \ 21 | --build-arg NODE_MAJOR="11" \ 22 | --build-arg YARN_VERSION="1.22.4" \ 23 | --build-arg BUNDLER_VERSION="2.1.0" \ 24 | --cache-from $(image_name):latest \ 25 | . \ 26 | -t $(image_name):$(latest_sha); 27 | 28 | build-latest: 29 | set -e; \ 30 | DOCKER_BUILDKIT=1 \ 31 | docker build \ 32 | -f Dockerfile.prod \ 33 | --build-arg RUBY_VERSION="2.6.5" \ 34 | --build-arg PG_MAJOR="11" \ 35 | --build-arg NODE_MAJOR="11" \ 36 | --build-arg YARN_VERSION="1.22.4" \ 37 | --build-arg BUNDLER_VERSION="2.1.0" \ 38 | . \ 39 | -t $(image_name):latest; 40 | 41 | push-latest: 42 | docker push $(image_name):latest 43 | 44 | push-sha: 45 | docker push $(image_name):$(latest_sha) 46 | 47 | build-push-latest: build-latest push-latest 48 | 49 | build-ci: build-sha-cached push-sha 50 | 51 | # DO_POSTGRES_URL needs to be set to the connection string in the shell 52 | upgrade-dev: 53 | helm upgrade $(project_name) helm --install \ 54 | --atomic --cleanup-on-fail --timeout=3m0s \ 55 | --set-string dbConnectionString=$(DO_POSTGRES_URL) 56 | 57 | build-push-latest-upgrade-dev: build-push-latest upgrade-dev 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Le Wagon x Digital Ocean Rails-on-Kubernetes demo 2 | 3 | :construction: This setup is a work in progress :construction: 4 | 5 | An attempt at creating a standardized Docker/Kubernetes-Helm/GitHub Actions setup for use with Rails projects. 6 | A version of this setup (that is little bit further ahead) is now used in production for services available to [Le Wagon](https://www.lewagon.com) teachers, students, and alumni. 7 | 8 | ## Demo application 9 | 10 | A standard Action Cable chat application (Rails 6 + Stimulus) that uses [twitter-text](https://github.com/twitter/twitter-text/tree/master/rb) gem to extract free-formed URLs out of text and generate Open Graph previews for links. The dependency was chosen consciously, as it requires an external system library to function (`libidn11`), which makes it a perfect case for Docker. 11 | 12 | It requires Sidekiq, Redis, and PostgreSQL to run, which represents the standard deployment stack of production Rails apps. 13 | 14 | ![The demo application at https://demo.lewagon.co](docs/app.png) 15 | 16 | ## Goals 17 | 18 | - Create a sensible approach to running fairly complex Rails projects in development with Docker 19 | - Generate reproducible and customizable configuration for production 20 | - Store everything related to deployments in the same repo as application code. Rely on GitHub one-way encrtypred secrets to store sensitive data like database connection strings. 21 | - Cater to a _"classic Rails stack"_: Rails, Puma, Sidekiq, Redis, PostgreSQL, Webpacker, Action Cable 22 | - Allow for only a certain amount of complexity: enough to fit inside a head of a single full-stack developer who may end up maintaining the projects. 23 | - Hide ugly details of Kubernetes management by relying on **1 environment/1 cluster** approach and letting Digital Ocean manage the master node. 24 | - Deploy with `git push origin master` or a PR merge on GitHub 25 | - Use any external Postgres database 26 | - Provide the easy migration from "Heroku way of doing things", once the application is past the MVP and needs to scale for a reasonable cost. 27 | 28 | ## Current status 29 | 30 | By constantly migrating legacy applications to this setup and starting new ones with the same setup in mind we make sure that all common production bugs will be squashed before we can _fully automate_ the generation of charts and configuration. 31 | 32 | ## Running locally 33 | 34 | We use [dip](https://github.com/bibendi/dip) by Misha Merkushin at [Evil Martians](https://evilmartians.com) to achieve a "classic CLI" feel while working with Docker under the hood. 35 | 36 | - `gem install dip` 37 | - `dip provision` to build all necessary images and setup the local DB 38 | - `dip up -d` to run all services in the background: `app`, `rails`, `sidekiq`, `webpacker` (runs `webpack-dev-server` for JS live reload), `postgres`, `redis`. Go to `localhost:3000` :tada: 39 | - `dip rails c` to visit console. 40 | - `dip down` to stop the music and go home. 41 | 42 | If you need to change project dependencies—run `dip bundle` and/or `dip yarn` after any changes to `Gemfile` or `package.json`. 43 | 44 | Check out `dip.yml` for other common shortcuts. 45 | 46 | Alternatively, you can launch `dip rails s`, `dip sidekiq`, and `dip webpacker` in respective tabs. This will allow for easier debugging with `byebug` (no need to attach to running Docker processes). 47 | 48 | ![Many-tabs workflow](docs/processes.png) 49 | 50 | ## Running in production 51 | 52 | - Provision a cluster from [Digital Ocean Managed Kubernetes](https://www.digitalocean.com/products/kubernetes/), install Nginx Ingress Controller, create DNS A record, set up [jetstack/certmanager](https://cert-manager.io/docs/installation/kubernetes/#installing-with-helm) for SSL. _Expect futher explanation of those steps once the setup is out of internal testing._ 53 | - Install Helm 3 on your machine. Run `make upgrade-dev` from project repo to deploy the release into the cluster. 54 | - Once you verified that the deployment from local machine works — feel free to fork the repo and set up the following secrets: 55 | 56 | ![Secrets to be set](docs/secrets.png) 57 | 58 | :warning: You don't have to use Quay as Docker image backend, in that case you can alter `Makefile` push commands to your own image hosting provider. 59 | 60 | - After you're done—a push to master will trigger a GitHub Action for deployment. See `deploy.yaml` for details. 61 | 62 | **:pray: Suggestions and PRs are welcome!** 63 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/do-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/app/assets/images/do-logo.png -------------------------------------------------------------------------------- /app/assets/images/wagon-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/app/assets/images/wagon-logo.png -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | identified_by :username 4 | 5 | def connect 6 | self.username = find_username 7 | end 8 | 9 | private 10 | 11 | def find_username 12 | if (username = cookies.encrypted[:username]) 13 | username 14 | else 15 | reject_unauthorized_connection 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/channels/messages_channel.rb: -------------------------------------------------------------------------------- 1 | class MessagesChannel < ApplicationCable::Channel 2 | def subscribed 3 | stream_from "messages" 4 | end 5 | 6 | def unsubscribed 7 | # Any cleanup needed when channel is unsubscribed 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | before_action :authenticate 3 | 4 | private 5 | 6 | def authenticate 7 | if (username = cookies.encrypted[:username].presence) 8 | Current.username = username 9 | else 10 | redirect_to new_login_path 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/logins_controller.rb: -------------------------------------------------------------------------------- 1 | class LoginsController < ApplicationController 2 | skip_before_action :authenticate 3 | 4 | def new 5 | redirect_to messages_path if cookies.encrypted[:username].present? 6 | end 7 | 8 | def create 9 | if params[:username].present? 10 | cookies.encrypted[:username] = params[:username] 11 | redirect_to messages_path 12 | else 13 | redirect_to new_login_path, alert: "Username can't be blank" unless username 14 | end 15 | end 16 | 17 | def destroy 18 | cookies.encrypted[:username] = nil 19 | redirect_to new_login_path 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/controllers/messages_controller.rb: -------------------------------------------------------------------------------- 1 | class MessagesController < ApplicationController 2 | def index 3 | @messages = Message.includes(:previews).last(20) 4 | end 5 | 6 | def create 7 | @message = Message.new(message_params) 8 | @message.author = Current.username 9 | if @message.save 10 | DeliverMessageJob.perform_later(@message.id) 11 | end 12 | end 13 | 14 | private 15 | 16 | def message_params 17 | params.require(:message).permit(:content, :delay) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/login_helper.rb: -------------------------------------------------------------------------------- 1 | module LoginHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/messages_helper.rb: -------------------------------------------------------------------------------- 1 | module MessagesHelper 2 | def flex_position(message, &block) 3 | if message.author == Current.username 4 | content_tag(:div, capture(&block), class: "d-flex flex-row-reverse") 5 | else 6 | content_tag(:div, capture(&block), class: "d-flex flex-row") 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/javascript/channels/consumer.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | 4 | import { createConsumer } from "@rails/actioncable" 5 | 6 | export default createConsumer() 7 | -------------------------------------------------------------------------------- /app/javascript/channels/index.js: -------------------------------------------------------------------------------- 1 | // Load all the channels within this directory and all subdirectories. 2 | // Channel files must be named *_channel.js. 3 | 4 | const channels = require.context('.', true, /_channel\.js$/) 5 | channels.keys().forEach(channels) 6 | -------------------------------------------------------------------------------- /app/javascript/controllers/chat_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus"; 2 | import consumer from "channels/consumer"; 3 | 4 | function subscribeConsumerWithStimulusContext(ctx) { 5 | consumer.subscriptions.create("MessagesChannel", { 6 | connected() { 7 | console.log("Hello from AC!"); 8 | }, 9 | 10 | disconnected() { 11 | console.log("Goodbye from AC"); 12 | }, 13 | 14 | received(message) { 15 | switch (message.type) { 16 | case "message": 17 | console.log(message); 18 | ctx.insertMessage(message); 19 | ctx.clearForm(message.author); 20 | break; 21 | case "preview": 22 | console.log(message); 23 | ctx.insertPreview(message); 24 | break; 25 | } 26 | } 27 | }); 28 | } 29 | 30 | export default class extends Controller { 31 | static targets = ["messages", "form"]; 32 | 33 | connect() { 34 | console.log("Stimulus chat controller up"); 35 | subscribeConsumerWithStimulusContext(this); 36 | this.messagesTarget.lastElementChild.scrollIntoView(); 37 | } 38 | 39 | insertMessage(message) { 40 | this.messagesTarget.insertAdjacentHTML("beforeend", message.html); 41 | const username = this.data.get("username"); 42 | const lastMessageRow = document.getElementById(`message-${message.id}`) 43 | .parentElement; 44 | if (message.author == username) { 45 | lastMessageRow.classList.replace("flex-row", "flex-row-reverse"); 46 | } 47 | lastMessageRow.scrollIntoView(); 48 | } 49 | 50 | insertPreview(preview) { 51 | const message = document.getElementById(`message-${preview.message_id}`); 52 | if (message) { 53 | const previews = message.querySelector(".previews"); 54 | previews.insertAdjacentHTML("afterbegin", preview.html); 55 | message.scrollIntoView(); 56 | } 57 | } 58 | 59 | clearForm(author) { 60 | if (author === this.data.get("username")) { 61 | this.formTarget.reset(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | // Load all the controllers within this directory and all subdirectories. 2 | // Controller files must be named *_controller.js. 3 | 4 | import { Application } from "stimulus" 5 | import { definitionsFromContext } from "stimulus/webpack-helpers" 6 | 7 | const application = Application.start() 8 | const context = require.context("controllers", true, /_controller\.js$/) 9 | application.load(definitionsFromContext(context)) 10 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | require("@rails/ujs").start(); 2 | require("turbolinks").start(); 3 | 4 | import "controllers"; 5 | import "../stylesheets/application"; 6 | -------------------------------------------------------------------------------- /app/javascript/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap"; 2 | @import "chat"; 3 | -------------------------------------------------------------------------------- /app/javascript/stylesheets/chat.scss: -------------------------------------------------------------------------------- 1 | #chat { 2 | height: 100vh; 3 | display: flex; 4 | flex-direction: column; 5 | 6 | #navbar-container { 7 | flex: auto; 8 | } 9 | 10 | #messages-container { 11 | height: 80vh; 12 | flex: auto; 13 | overflow-y: auto; 14 | -webkit-overflow-scrolling: touch; 15 | } 16 | 17 | #chat-controls-container { 18 | flex: auto; 19 | } 20 | } 21 | 22 | .message-box { 23 | width: fit-content; 24 | } 25 | 26 | .preview-box { 27 | max-width: 500px; 28 | } 29 | 30 | // Disable these elements on xs screens: 31 | 32 | #chat-submit { 33 | display: none; 34 | } 35 | 36 | #chat-title { 37 | display: none; 38 | } 39 | 40 | // =================================== 41 | 42 | @media (min-width: 576px) { 43 | #chat-submit { 44 | display: inline; 45 | } 46 | 47 | #chat-title { 48 | display: inline; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /app/jobs/deliver_message_job.rb: -------------------------------------------------------------------------------- 1 | class DeliverMessageJob < ApplicationJob 2 | queue_as :default 3 | sidekiq_options retry: 0 # never retry 4 | 5 | def perform(message_id) 6 | @message = Message.find(message_id) 7 | MessageBroadcaster.call(@message) 8 | count_created = PreviewCreator.call(@message) 9 | PreviewBroadcaster.call(@message) if count_created.positive? 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/current.rb: -------------------------------------------------------------------------------- 1 | class Current < ActiveSupport::CurrentAttributes 2 | attribute :username 3 | end 4 | -------------------------------------------------------------------------------- /app/models/message.rb: -------------------------------------------------------------------------------- 1 | class Message < ApplicationRecord 2 | has_many :previews, dependent: :destroy 3 | # gives extract_urls method to message 4 | include Twitter::TwitterText::Extractor 5 | validates :content, presence: true 6 | 7 | def urls 8 | extract_urls(content).map do |url| 9 | scheme_present?(url) ? url : "http://#{url}" 10 | end 11 | end 12 | 13 | private 14 | 15 | def scheme_present?(url) 16 | URI.parse(url).scheme.present? 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/models/preview.rb: -------------------------------------------------------------------------------- 1 | class Preview < ApplicationRecord 2 | belongs_to :message 3 | end 4 | -------------------------------------------------------------------------------- /app/services/message_broadcaster.rb: -------------------------------------------------------------------------------- 1 | class MessageBroadcaster 2 | def self.call(message) 3 | new(message).broadcast_message 4 | end 5 | 6 | def initialize(message) 7 | @message = message 8 | end 9 | 10 | def broadcast_message 11 | ActionCable.server.broadcast( 12 | "messages", 13 | type: "message", 14 | id: @message.id, 15 | author: @message.author, 16 | html: ApplicationController.renderer.render( 17 | partial: "messages/message", 18 | locals: {message: @message} 19 | ) 20 | ) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/services/preview_broadcaster.rb: -------------------------------------------------------------------------------- 1 | class PreviewBroadcaster 2 | def self.call(message) 3 | new(message).broadcast_previews 4 | end 5 | 6 | def initialize(message) 7 | @message = message 8 | end 9 | 10 | def broadcast_previews 11 | @message.previews.each do |preview| 12 | broadcast_preview(preview) 13 | end 14 | end 15 | 16 | private 17 | 18 | def broadcast_preview(preview) 19 | ActionCable.server.broadcast( 20 | "messages", 21 | type: "preview", 22 | id: preview.id, 23 | message_id: preview.message.id, 24 | html: ApplicationController.renderer.render( 25 | partial: "previews/preview", 26 | locals: {preview: preview} 27 | ) 28 | ) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/services/preview_creator.rb: -------------------------------------------------------------------------------- 1 | class PreviewCreator 2 | def self.call(message) 3 | new(message).create_previews 4 | end 5 | 6 | def initialize(message) 7 | @message = message 8 | end 9 | 10 | def create_previews 11 | return 0 unless @message.urls.any? 12 | 13 | attrs = @message.urls.map { |url| 14 | # This makes an external HTTP call 15 | # TODO: offload to another job? 16 | ogp = OpenGraph.new(url) 17 | { 18 | title: ogp.title, 19 | description: ogp.description, 20 | image_url: ogp.images.first, 21 | url: url, 22 | } 23 | } 24 | @message.previews.create(attrs) 25 | @message.previews.count 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Le Wagon/Digital Ocean Rails DOKS demo 5 | <%= csrf_meta_tags %> 6 | <%= csp_meta_tag %> 7 | 8 | 9 | 10 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload', defer: true %> 11 | <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload', defer: true %> 12 | <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload', defer: true %> 13 | 14 | 15 | 16 | <% if flash[:alert] %> 17 |

<%= flash[:alert] %>

18 | <% end %> 19 | 20 | <%= yield %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/logins/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with url: login_path do |f| %> 2 |
3 |
4 | <%= f.text_field :username, class: "form-control" %> 5 |
6 |
7 | <%= f.submit "Set Username", class: "btn btn-primary" %> 8 |
9 |
10 | <% end %> 11 | -------------------------------------------------------------------------------- /app/views/messages/_message.html.erb: -------------------------------------------------------------------------------- 1 | <%= flex_position(message) do %> 2 |
5 |
6 | <%= message.content %> 7 |

8 | 9 | <%= message.author %> 10 | <%= l(message.created_at, format: :short) %> 11 | 12 |

13 |
14 |
15 | <%= render message.previews %> 16 |
17 |
18 | <% end %> 19 | -------------------------------------------------------------------------------- /app/views/messages/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 25 | 26 |
27 |
28 | <%= render @messages %> 29 |
30 |
31 | 32 |
33 |
34 | <%= form_with model: Message.new, 35 | id: "chat_form", 36 | html: { 37 | autocomplete: "off" 38 | }, 39 | data: { 40 | target: "chat.form" 41 | } do |f| 42 | %> 43 |
44 |
45 | <%= f.text_field :content, 46 | class: "form-control", 47 | placeholder: "💬 This chat supports URL previews (e.g., lewagon.com)" 48 | %> 49 |
50 |
51 | <%= f.submit "🚀", class: "btn btn-outline-primary" %> 52 |
53 |
54 | <% end %> 55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /app/views/previews/_preview.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= image_tag preview.image_url, width: 64, class: "mr-2" if preview.image_url.present? %> 3 |
4 |
5 | <%= link_to preview.title, preview.url, class: "text-decoration-none" %> 6 |
7 | 8 | <%= preview.description %> 9 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | var validEnv = ['development', 'test', 'production'] 3 | var currentEnv = api.env() 4 | var isDevelopmentEnv = api.env('development') 5 | var isProductionEnv = api.env('production') 6 | var isTestEnv = api.env('test') 7 | 8 | if (!validEnv.includes(currentEnv)) { 9 | throw new Error( 10 | 'Please specify a valid `NODE_ENV` or ' + 11 | '`BABEL_ENV` environment variables. Valid values are "development", ' + 12 | '"test", and "production". Instead, received: ' + 13 | JSON.stringify(currentEnv) + 14 | '.' 15 | ) 16 | } 17 | 18 | return { 19 | presets: [ 20 | isTestEnv && [ 21 | require('@babel/preset-env').default, 22 | { 23 | targets: { 24 | node: 'current' 25 | } 26 | } 27 | ], 28 | (isProductionEnv || isDevelopmentEnv) && [ 29 | require('@babel/preset-env').default, 30 | { 31 | forceAllTransforms: true, 32 | useBuiltIns: 'entry', 33 | corejs: 3, 34 | modules: false, 35 | exclude: ['transform-typeof-symbol'] 36 | } 37 | ] 38 | ].filter(Boolean), 39 | plugins: [ 40 | require('babel-plugin-macros'), 41 | require('@babel/plugin-syntax-dynamic-import').default, 42 | isTestEnv && require('babel-plugin-dynamic-import-node'), 43 | require('@babel/plugin-transform-destructuring').default, 44 | [ 45 | require('@babel/plugin-proposal-class-properties').default, 46 | { 47 | loose: true 48 | } 49 | ], 50 | [ 51 | require('@babel/plugin-proposal-object-rest-spread').default, 52 | { 53 | useBuiltIns: true 54 | } 55 | ], 56 | [ 57 | require('@babel/plugin-transform-runtime').default, 58 | { 59 | helpers: false, 60 | regenerator: true, 61 | corejs: false 62 | } 63 | ], 64 | [ 65 | require('@babel/plugin-transform-regenerator').default, 66 | { 67 | async: false 68 | } 69 | ] 70 | ].filter(Boolean) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 || ">= 0.a" 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= begin 65 | env_var_version || cli_arg_version || 66 | lockfile_version || "#{Gem::Requirement.default}.a" 67 | end 68 | end 69 | 70 | def load_bundler! 71 | ENV["BUNDLE_GEMFILE"] ||= gemfile 72 | 73 | # must dup string for RG < 1.8 compatibility 74 | activate_bundler(bundler_version.dup) 75 | end 76 | 77 | def activate_bundler(bundler_version) 78 | if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0") 79 | bundler_version = "< 2" 80 | end 81 | gem_error = activation_error_handling do 82 | gem "bundler", bundler_version 83 | end 84 | return if gem_error.nil? 85 | require_error = activation_error_handling do 86 | require "bundler/version" 87 | end 88 | return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 89 | warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`" 90 | exit 42 91 | end 92 | 93 | def activation_error_handling 94 | yield 95 | nil 96 | rescue StandardError, LoadError => e 97 | e 98 | end 99 | end 100 | 101 | m.load_bundler! 102 | 103 | if m.invoked_as_script? 104 | load Gem.bin_path("bundler", "bundle") 105 | end 106 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path('..', __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to setup or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies 21 | # system('bin/yarn') 22 | 23 | # puts "\n== Copying sample files ==" 24 | # unless File.exist?('config/database.yml') 25 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' 26 | # end 27 | 28 | puts "\n== Preparing database ==" 29 | system! 'bin/rails db:prepare' 30 | 31 | puts "\n== Removing old logs and tempfiles ==" 32 | system! 'bin/rails log:clear tmp:clear' 33 | 34 | puts "\n== Restarting application server ==" 35 | system! 'bin/rails restart' 36 | end 37 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads Spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == 'spring' } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "rubygems" 11 | require "bundler/setup" 12 | 13 | require "webpacker" 14 | require "webpacker/webpack_runner" 15 | 16 | APP_ROOT = File.expand_path("..", __dir__) 17 | Dir.chdir(APP_ROOT) do 18 | Webpacker::WebpackRunner.run(ARGV) 19 | end 20 | -------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "rubygems" 11 | require "bundler/setup" 12 | 13 | require "webpacker" 14 | require "webpacker/dev_server_runner" 15 | 16 | APP_ROOT = File.expand_path("..", __dir__) 17 | Dir.chdir(APP_ROOT) do 18 | Webpacker::DevServerRunner.run(ARGV) 19 | end 20 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | begin 5 | exec "yarnpkg", *ARGV 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | require "active_record/railtie" 8 | # require "active_storage/engine" 9 | require "action_controller/railtie" 10 | require "action_mailer/railtie" 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | require "action_view/railtie" 14 | require "action_cable/engine" 15 | require "sprockets/railtie" 16 | require "rails/test_unit/railtie" 17 | 18 | # Require the gems listed in Gemfile, including any gems 19 | # you've limited to :test, :development, or :production. 20 | Bundler.require(*Rails.groups) 21 | 22 | module RailsK8sDemo 23 | class Application < Rails::Application 24 | # Initialize configuration defaults for originally generated Rails version. 25 | config.load_defaults 6.0 26 | 27 | # Settings in config/environments/* take precedence over those specified here. 28 | # Application configuration can go into files in config/initializers 29 | # -- all .rb files in that directory are automatically loaded after loading 30 | # the framework and any gems in your application. 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: redis 3 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 4 | 5 | test: 6 | adapter: test 7 | 8 | production: 9 | adapter: redis 10 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 11 | channel_prefix: review_scraper_production 12 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | zbCgZmsmpEUg7w0Nve4qHU2lHvKDXkzCSLR98vGVUTapJAc+7abv6NxoFEHCnc5AEaJa3rJjPRuA0zEox2vQlge5Y6cdGTloXNhHDN7k4I+jDiXggxNYiV2UbpOPsKTz+n8gDxclJQOQUpz3UCrUKxlluAirva31FAkHkvSqjEuRkXdUH5vN3GVW/oRyC1zsbx4Q8pb5A+EKFzPAT/lAxZjkvOVo2EDiavTCHV/xND0mP1kqU6RrlGO7GVdlRxHZW6nKi7nwdi7lW5Yqqn+PZoL8P8/QsxJB2lL4VV3qjnnN93GBBvqN/46SORvAKB4llGcCjwQ9KV3UMSs9zSY2vpuaFMd9TOobxF5lCiqHGPAcHz0xt+9ZgvZX0yAegVkdEfw8r2lPixaBCEiizmtIHHWAlUfcxJx4j6jN--VVJAk/F+B7CvG485--BFKaGf6oTPwHYzHKcH1yLw== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | encoding: unicode 4 | # For details on connection pooling, see Rails configuration guide 5 | # https://guides.rubyonrails.org/configuring.html#database-pooling 6 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 7 | 8 | development: 9 | <<: *default 10 | database: db_development 11 | url: <%= ENV['DATABASE_URL'] %> 12 | 13 | test: 14 | <<: *default 15 | database: db_test 16 | url: <%= ENV['DATABASE_URL'] %> 17 | 18 | production: 19 | <<: *default 20 | database: rails-k8s-demo-production 21 | url: <%= ENV['DATABASE_URL'] %> 22 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Docker will expose port on one of those subnets, so we permit them 5 | config.web_console.permissions = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] 6 | 7 | # Sadly, not documented anywhere, but required since Rails 6 8 | config.hosts << ".ngrok.io" 9 | 10 | # In the development environment your application's code is reloaded on 11 | # every request. This slows down response time but is perfect for development 12 | # since you don't have to restart the web server when you make code changes. 13 | config.cache_classes = false 14 | 15 | # Do not eager load code on boot. 16 | config.eager_load = false 17 | 18 | # Show full error reports. 19 | config.consider_all_requests_local = true 20 | 21 | # Enable/disable caching. By default caching is disabled. 22 | # Run rails dev:cache to toggle caching. 23 | if Rails.root.join("tmp", "caching-dev.txt").exist? 24 | config.action_controller.perform_caching = true 25 | config.action_controller.enable_fragment_cache_logging = true 26 | 27 | config.cache_store = :memory_store 28 | config.public_file_server.headers = { 29 | "Cache-Control" => "public, max-age=#{2.days.to_i}", 30 | } 31 | else 32 | config.action_controller.perform_caching = false 33 | 34 | config.cache_store = :null_store 35 | end 36 | 37 | config.active_job.queue_adapter = :sidekiq 38 | 39 | # Don't care if the mailer can't send. 40 | config.action_mailer.raise_delivery_errors = false 41 | 42 | config.action_mailer.perform_caching = false 43 | 44 | # Print deprecation notices to the Rails logger. 45 | config.active_support.deprecation = :log 46 | 47 | # Raise an error on page load if there are pending migrations. 48 | config.active_record.migration_error = :page_load 49 | 50 | # Highlight code that triggered database queries in logs. 51 | config.active_record.verbose_query_logs = true 52 | 53 | # Debug mode disables concatenation and preprocessing of assets. 54 | # This option may cause significant delays in view rendering with a large 55 | # number of complex assets. 56 | config.assets.debug = true 57 | 58 | # Suppress logger output for asset requests. 59 | config.assets.quiet = true 60 | 61 | # Raises error for missing translations. 62 | # config.action_view.raise_on_missing_translations = true 63 | 64 | # Use an evented file watcher to asynchronously detect changes in source code, 65 | # routes, locales, etc. This feature depends on the listen gem. 66 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 67 | end 68 | 69 | HttpLog.configure do |config| 70 | # Enable or disable all logging 71 | config.enabled = true 72 | 73 | # You can assign a different logger or method to call on that logger 74 | config.logger = Logger.new($stdout) 75 | config.logger_method = :log 76 | 77 | # I really wouldn't change this... 78 | config.severity = Logger::Severity::DEBUG 79 | 80 | # Tweak which parts of the HTTP cycle to log... 81 | config.log_connect = true 82 | config.log_request = true 83 | config.log_headers = true 84 | config.log_data = true 85 | config.log_status = true 86 | config.log_response = false 87 | config.log_benchmark = true 88 | 89 | # ...or log all request as a single line by setting this to `true` 90 | config.compact_log = true 91 | 92 | # You can also log in JSON format 93 | config.json_log = false 94 | 95 | # Prettify the output - see below 96 | config.color = false 97 | 98 | # Limit logging based on URL patterns 99 | config.url_whitelist_pattern = nil 100 | config.url_blacklist_pattern = nil 101 | end 102 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 19 | config.require_master_key = true 20 | 21 | # Disable serving static files from the `/public` folder by default since 22 | # Apache or NGINX already handles this. 23 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 24 | 25 | # Compress CSS using a preprocessor. 26 | # config.assets.css_compressor = :sass 27 | 28 | # Do not fallback to assets pipeline if a precompiled asset is missed. 29 | config.assets.compile = false 30 | 31 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 32 | config.action_controller.asset_host = "dibjvtbcs979a.cloudfront.net" 33 | 34 | # Specifies the header that your server uses for sending files. 35 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 36 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 37 | 38 | # Mount Action Cable outside main process or domain. 39 | # config.action_cable.mount_path = nil 40 | # config.action_cable.url = 'wss://example.com/cable' 41 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 42 | 43 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 44 | config.force_ssl = true 45 | 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | # Prepend all log lines with the following tags. 52 | config.log_tags = [:request_id] 53 | 54 | # Use a different cache store in production. 55 | # config.cache_store = :mem_cache_store 56 | 57 | # Use a real queuing backend for Active Job (and separate queues per environment). 58 | config.active_job.queue_adapter = :sidekiq 59 | # config.active_job.queue_name_prefix = "review_scraper_production" 60 | 61 | config.action_mailer.perform_caching = false 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Use default logging formatter so that PID and timestamp are not suppressed. 75 | config.log_formatter = ::Logger::Formatter.new 76 | 77 | # Use a different logger for distributed setups. 78 | # require 'syslog/logger' 79 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 80 | 81 | if ENV["RAILS_LOG_TO_STDOUT"].present? 82 | logger = ActiveSupport::Logger.new(STDOUT) 83 | logger.formatter = config.log_formatter 84 | config.logger = ActiveSupport::TaggedLogging.new(logger) 85 | end 86 | 87 | # Do not dump schema after migrations. 88 | config.active_record.dump_schema_after_migration = false 89 | 90 | # Inserts middleware to perform automatic connection switching. 91 | # The `database_selector` hash is used to pass options to the DatabaseSelector 92 | # middleware. The `delay` is used to determine how long to wait after a write 93 | # to send a subsequent read to the primary. 94 | # 95 | # The `database_resolver` class is used by the middleware to determine which 96 | # database is appropriate to use based on the time delay. 97 | # 98 | # The `database_resolver_context` class is used by the middleware to set 99 | # timestamps for the last write to the primary. The resolver uses the context 100 | # class timestamps to determine how long to wait before reading from the 101 | # replica. 102 | # 103 | # By default Rails will store a last write timestamp in the session. The 104 | # DatabaseSelector middleware is designed as such you can define your own 105 | # strategy for connection switching and pass that into the middleware through 106 | # these configuration options. 107 | # config.active_record.database_selector = { delay: 2.seconds } 108 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver 109 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session 110 | end 111 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # The test environment is used exclusively to run your application's 2 | # test suite. You never need to work with it otherwise. Remember that 3 | # your test database is "scratch space" for the test suite and is wiped 4 | # and recreated between test runs. Don't rely on the data there! 5 | 6 | Rails.application.configure do 7 | # Settings specified here will take precedence over those in config/application.rb. 8 | 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. This avoids loading your whole application 12 | # just for the purpose of running a single test. If you are using a tool that 13 | # preloads Rails for running tests, you may have to set it to true. 14 | config.eager_load = false 15 | 16 | # Configure public file server for tests with Cache-Control for performance. 17 | config.public_file_server.enabled = true 18 | config.public_file_server.headers = { 19 | "Cache-Control" => "public, max-age=#{1.hour.to_i}", 20 | } 21 | 22 | # Show full error reports and disable caching. 23 | config.consider_all_requests_local = true 24 | config.action_controller.perform_caching = false 25 | config.cache_store = :null_store 26 | 27 | # Raise exceptions instead of rendering exception templates. 28 | config.action_dispatch.show_exceptions = false 29 | 30 | # Disable request forgery protection in test environment. 31 | config.action_controller.allow_forgery_protection = false 32 | 33 | config.action_mailer.perform_caching = false 34 | 35 | # Tell Action Mailer not to deliver emails to the real world. 36 | # The :test delivery method accumulates sent emails in the 37 | # ActionMailer::Base.deliveries array. 38 | config.action_mailer.delivery_method = :test 39 | 40 | # Print deprecation notices to the stderr. 41 | config.active_support.deprecation = :stderr 42 | 43 | # Raises error for missing translations. 44 | # config.action_view.raise_on_missing_translations = true 45 | end 46 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join("node_modules") 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | # # If you are using webpack-dev-server then specify webpack-dev-server host 15 | # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? 16 | 17 | # # Specify URI for violation reports 18 | # # policy.report_uri "/csp-violation-report-endpoint" 19 | # end 20 | 21 | # If you are using UJS then enable automatic nonce generation 22 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 23 | 24 | # Set the nonce only to specific directives 25 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src) 26 | 27 | # Report CSP violations to a specified URI 28 | # For further information see the following documentation: 29 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 30 | # Rails.application.config.content_security_policy_report_only = true 31 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/sidekiq_redis.rb: -------------------------------------------------------------------------------- 1 | $redis = Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0"), password: ENV["REDIS_PASSWORD"]) 2 | 3 | Sidekiq.configure_server do |config| 4 | config.redis = {url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0"), password: ENV["REDIS_PASSWORD"]} 5 | end 6 | 7 | Sidekiq.configure_client do |config| 8 | config.redis = {url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0"), password: ENV["REDIS_PASSWORD"]} 9 | end 10 | 11 | Redis.exists_returns_integer = true 12 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 12 | # 13 | port ENV.fetch("PORT") { 3000 } 14 | 15 | # Specifies the `environment` that Puma will run in. 16 | # 17 | environment ENV.fetch("RAILS_ENV") { "development" } 18 | 19 | # Specifies the `pidfile` that Puma will use. 20 | # pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 21 | 22 | # Specifies the number of `workers` to boot in clustered mode. 23 | # Workers are forked web server processes. If using threads and workers together 24 | # the concurrency of the application would be max `threads` * `workers`. 25 | # Workers do not work on JRuby or Windows (both of which do not support 26 | # processes). 27 | # 28 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 29 | 30 | # Use the `preload_app!` method when specifying a `workers` number. 31 | # This directive tells Puma to first boot the application and load code 32 | # before forking the application. This takes advantage of Copy On Write 33 | # process behavior so workers use less memory. 34 | # 35 | # preload_app! 36 | 37 | # Allow puma to be restarted by `rails restart` command. 38 | plugin :tmp_restart 39 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | root to: "logins#new" 3 | resource :login, only: [:new, :create, :destroy] 4 | 5 | resources :messages, only: [:index, :create] 6 | 7 | # Sidekiq dashboard 8 | require "sidekiq/web" 9 | mount Sidekiq::Web, at: "/sidekiq" 10 | end 11 | -------------------------------------------------------------------------------- /config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | :concurrency: 8 2 | :timeout: 30 3 | :verbose: true 4 | :queues: 5 | - default 6 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | Spring.watch( 2 | ".ruby-version", 3 | ".rbenv-vars", 4 | "tmp/restart.txt", 5 | "tmp/caching-dev.txt" 6 | ) 7 | -------------------------------------------------------------------------------- /config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { environment } = require('@rails/webpacker') 2 | 3 | const webpack = require('webpack') 4 | environment.plugins.append( 5 | 'Provide', 6 | new webpack.ProvidePlugin({ 7 | $: 'jquery', 8 | jQuery: 'jquery', 9 | Popper: ['popper.js', 'default'] 10 | }) 11 | ) 12 | 13 | module.exports = environment 14 | -------------------------------------------------------------------------------- /config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpack-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/javascript 5 | source_entry_path: packs 6 | public_root_path: public 7 | public_output_path: packs 8 | cache_path: tmp/cache/webpacker 9 | check_yarn_integrity: false 10 | webpack_compile_output: false 11 | 12 | # Additional paths webpack should lookup modules 13 | # ['app/assets', 'engine/foo/app/assets'] 14 | resolved_paths: [] 15 | 16 | # Reload manifest.json on all requests so we reload latest compiled packs 17 | cache_manifest: false 18 | 19 | # Extract and emit a css file 20 | extract_css: false 21 | 22 | static_assets_extensions: 23 | - .jpg 24 | - .jpeg 25 | - .png 26 | - .gif 27 | - .tiff 28 | - .ico 29 | - .svg 30 | - .eot 31 | - .otf 32 | - .ttf 33 | - .woff 34 | - .woff2 35 | 36 | extensions: 37 | - .mjs 38 | - .js 39 | - .sass 40 | - .scss 41 | - .css 42 | - .module.sass 43 | - .module.scss 44 | - .module.css 45 | - .png 46 | - .svg 47 | - .gif 48 | - .jpeg 49 | - .jpg 50 | 51 | development: 52 | <<: *default 53 | compile: true 54 | 55 | # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules 56 | check_yarn_integrity: true 57 | 58 | # Reference: https://webpack.js.org/configuration/dev-server/ 59 | dev_server: 60 | https: false 61 | host: localhost 62 | port: 3035 63 | public: localhost:3035 64 | hmr: false 65 | # Inline should be set to true if using HMR 66 | inline: true 67 | overlay: true 68 | compress: true 69 | disable_host_check: true 70 | use_local_ip: false 71 | quiet: false 72 | headers: 73 | "Access-Control-Allow-Origin": "*" 74 | watch_options: 75 | ignored: "**/node_modules/**" 76 | poll: true 77 | 78 | test: 79 | <<: *default 80 | compile: true 81 | 82 | # Compile test packs to a separate directory 83 | public_output_path: packs-test 84 | 85 | production: 86 | <<: *default 87 | 88 | # Production depends on precompilation of packs prior to booting for performance. 89 | compile: false 90 | 91 | # Extract and emit a css file 92 | extract_css: true 93 | 94 | # Cache manifest.json for performance 95 | cache_manifest: true 96 | -------------------------------------------------------------------------------- /db/migrate/20191126104811_create_messages.rb: -------------------------------------------------------------------------------- 1 | class CreateMessages < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :messages do |t| 4 | t.string :content 5 | t.string :author 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20191203142803_create_previews.rb: -------------------------------------------------------------------------------- 1 | class CreatePreviews < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :previews do |t| 4 | t.string :title 5 | t.string :url 6 | t.string :image_url 7 | t.text :description 8 | t.references :message, null: false, foreign_key: true 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `rails 6 | # db:schema:load`. When creating a new database, `rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2019_12_03_142803) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "plpgsql" 17 | 18 | create_table "messages", force: :cascade do |t| 19 | t.string "content" 20 | t.string "author" 21 | t.datetime "created_at", precision: 6, null: false 22 | t.datetime "updated_at", precision: 6, null: false 23 | end 24 | 25 | create_table "previews", force: :cascade do |t| 26 | t.string "title" 27 | t.string "url" 28 | t.string "image_url" 29 | t.text "description" 30 | t.bigint "message_id", null: false 31 | t.datetime "created_at", precision: 6, null: false 32 | t.datetime "updated_at", precision: 6, null: false 33 | t.index ["message_id"], name: "index_previews_on_message_id" 34 | end 35 | 36 | add_foreign_key "previews", "messages" 37 | end 38 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | -------------------------------------------------------------------------------- /dip.yml: -------------------------------------------------------------------------------- 1 | # Run eval "$(dip console)" in open terminal tab to not have to to prepend 2 | # commands with `dip`: 3 | # `dip rails c` -> `rails c` 4 | 5 | # Required minimum dip version 6 | version: "4.1" 7 | 8 | environment: 9 | COMPOSE_EXT: development 10 | 11 | compose: 12 | files: 13 | - docker-compose.yml 14 | project_name: rails-k8s-demo 15 | 16 | interaction: 17 | bash: 18 | description: Open the Bash shell in rails container 19 | service: app 20 | command: bash 21 | compose: 22 | run_options: [no-deps] 23 | 24 | bundle: 25 | description: Run Bundler commands 26 | service: app 27 | command: bundle 28 | 29 | yarn: 30 | description: Run Yarn commands 31 | service: app 32 | command: yarn 33 | 34 | rake: 35 | description: Run Rake commands 36 | service: app 37 | command: bundle exec rake 38 | 39 | standardrb: 40 | description: Run standardrb on Project 41 | service: app 42 | command: bundle exec standardrb 43 | 44 | rspec: 45 | description: Run Rspec commands 46 | service: app 47 | environment: 48 | RAILS_ENV: test 49 | command: ./vnc.sh bundle exec rspec --format=documentation 50 | compose: 51 | run_options: ["publish=5900:5900"] 52 | 53 | sidekiq: 54 | description: Run commands in sidekiq container 55 | service: sidekiq 56 | compose: 57 | method: run 58 | subcommands: 59 | logs: 60 | description: "Display last 200 lines of Sidekiq logs and follow" 61 | compose: 62 | method: logs 63 | run_options: [follow, tail='200'] 64 | 65 | rails: 66 | description: Run Rails commands 67 | service: app 68 | command: bundle exec rails 69 | subcommands: 70 | s: 71 | description: Run Rails server at http://localhost:3000 72 | service: rails 73 | compose: 74 | run_options: [service-ports, use-aliases] 75 | s-altport: 76 | description: Run Rails server at select port `dip run -p 5000:3000 rails s-altport` 77 | service: rails 78 | compose: 79 | run_options: [use-aliases] 80 | logs: 81 | description: Display last 200 lines of Rails logs and follow 82 | service: rails 83 | compose: 84 | method: logs 85 | run_options: [follow, tail='200'] 86 | 87 | webpacker: 88 | description: Run commands towards Webpacker service 89 | service: webpacker 90 | subcommands: 91 | logs: 92 | description: Display last 200 lines of Webpacker logs and follow 93 | compose: 94 | method: logs 95 | run_options: [follow, tail='200'] 96 | compose: 97 | run_options: [service-ports, use-aliases] 98 | 99 | psql: 100 | description: Run Postgres psql console 101 | service: app 102 | default_args: forms_development 103 | command: psql -h postgres -U postgres 104 | 105 | provision: 106 | - docker-compose down --volumes 107 | - docker-compose up -d postgres redis 108 | - docker-compose up -d --build app 109 | - dip bundle install 110 | - dip yarn install 111 | - "dip rails db:prepare" 112 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | 3 | # General config for app, rails, sidekiq, webpacker 4 | # Magic from https://evilmartians.com/chronicles/ruby-on-whales-docker-for-ruby-rails-development 5 | 6 | x-template-app: &template-app 7 | build: 8 | context: . 9 | dockerfile: Dockerfile 10 | args: 11 | BUNDLER_VERSION: "2.1.4" 12 | image: rails-k8s-demo:dev 13 | tmpfs: 14 | - /tmp 15 | stdin_open: true 16 | tty: true 17 | volumes: 18 | - .:/app:cached 19 | - rails_cache:/app/tmp/cache 20 | - bundle:/usr/local/bundle 21 | - node_modules:/app/node_modules 22 | - packs:/app/public/packs 23 | - .psqlrc:/root/.psqlrc:ro 24 | environment: 25 | - NODE_ENV=development 26 | - RAILS_ENV=${RAILS_ENV:-development} 27 | - REDIS_URL=redis://redis:6379/ 28 | - DATABASE_URL=postgres://postgres:postgres@postgres:5432 29 | - BOOTSNAP_CACHE_DIR=/usr/local/bundle/bootsnap 30 | - WEBPACKER_DEV_SERVER_HOST=webpacker 31 | - WEB_CONCURRENCY=1 32 | - HISTFILE=/app/log/.bash_history 33 | - PSQL_HISTFILE=/app/log/.psql_history 34 | - EDITOR=vi 35 | 36 | services: 37 | app: &app 38 | <<: *template-app 39 | command: irb 40 | depends_on: 41 | - postgres 42 | - redis 43 | 44 | rails: 45 | <<: *template-app 46 | command: 47 | - "bash" 48 | - "-c" 49 | - > 50 | rm -f /app/tmp/pids/server.pid && 51 | bundle exec rails server -b 0.0.0.0 52 | ports: 53 | - "3000:3000" 54 | depends_on: 55 | - app 56 | 57 | sidekiq: 58 | <<: *template-app 59 | command: bundle exec sidekiq -C config/sidekiq.yml 60 | depends_on: 61 | - app 62 | 63 | webpacker: 64 | <<: *template-app 65 | command: ./bin/webpack-dev-server 66 | ports: 67 | - "3035:3035" 68 | volumes: 69 | - .:/app:cached 70 | - bundle:/usr/local/bundle 71 | - node_modules:/app/node_modules 72 | - packs:/app/public/packs 73 | environment: 74 | - WEBPACKER_DEV_SERVER_HOST=0.0.0.0 75 | - NODE_ENV=${NODE_ENV:-development} 76 | - RAILS_ENV=${RAILS_ENV:-development} 77 | 78 | postgres: 79 | image: postgres:11.1 80 | volumes: 81 | - .psqlrc:/root/.psqlrc:ro 82 | - postgres:/var/lib/postgresql/data 83 | - ./log:/root/log:cached 84 | environment: 85 | - PSQL_HISTFILE=/root/log/.psql_history 86 | ports: 87 | - 5432 88 | 89 | redis: 90 | image: redis:4.0.14-alpine 91 | volumes: 92 | - redis:/data 93 | ports: 94 | - 6379 95 | 96 | volumes: 97 | postgres: 98 | redis: 99 | bundle: 100 | node_modules: 101 | rails_cache: 102 | packs: 103 | -------------------------------------------------------------------------------- /docs/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/docs/app.png -------------------------------------------------------------------------------- /docs/processes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/docs/processes.png -------------------------------------------------------------------------------- /docs/secrets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/docs/secrets.png -------------------------------------------------------------------------------- /helm/Chart.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: redis 3 | repository: https://kubernetes-charts.storage.googleapis.com/ 4 | version: 10.2.1 5 | digest: sha256:c84f28b553f023b9b9afbb4ec56152545cbc70b228d9981fec4f9ae01b5838b4 6 | generated: "2020-06-17T21:58:23.613426+02:00" 7 | -------------------------------------------------------------------------------- /helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: rails-k8s-demo 3 | description: A Helm chart for Kubernetes 4 | type: application 5 | version: 0.1.0 6 | appVersion: 1.0 7 | 8 | dependencies: 9 | - name: redis 10 | version: 10.2.1 11 | repository: "@stable" 12 | -------------------------------------------------------------------------------- /helm/charts/redis-10.2.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/helm/charts/redis-10.2.1.tgz -------------------------------------------------------------------------------- /helm/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "rails-k8s-demo.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "rails-k8s-demo.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "rails-k8s-demo.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "rails-k8s-demo.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | echo "Visit http://127.0.0.1:8080 to use your application" 20 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /helm/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "rails-k8s-demo.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "rails-k8s-demo.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "rails-k8s-demo.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "rails-k8s-demo.labels" -}} 38 | helm.sh/chart: {{ include "rails-k8s-demo.chart" . }} 39 | {{ include "rails-k8s-demo.selectorLabels" . }} 40 | {{- if .Chart.AppVersion }} 41 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 42 | {{- end }} 43 | app.kubernetes.io/managed-by: {{ .Release.Service }} 44 | {{- end -}} 45 | 46 | {{/* 47 | Selector labels 48 | */}} 49 | {{- define "rails-k8s-demo.selectorLabels" -}} 50 | app.kubernetes.io/name: {{ include "rails-k8s-demo.name" . }} 51 | app.kubernetes.io/instance: {{ .Release.Name }} 52 | {{- end -}} 53 | 54 | {{/* 55 | Create the name of the service account to use 56 | */}} 57 | {{- define "rails-k8s-demo.serviceAccountName" -}} 58 | {{- if .Values.serviceAccount.create -}} 59 | {{ default (include "rails-k8s-demo.fullname" .) .Values.serviceAccount.name }} 60 | {{- else -}} 61 | {{ default "default" .Values.serviceAccount.name }} 62 | {{- end -}} 63 | {{- end -}} 64 | -------------------------------------------------------------------------------- /helm/templates/deployments/rails.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "rails-k8s-demo.fullname" . }}-rails 5 | labels: 6 | {{- include "rails-k8s-demo.labels" . | nindent 4 }} 7 | spec: 8 | replicas: {{ .Values.rails.replicaCount }} 9 | selector: 10 | matchLabels: 11 | {{- include "rails-k8s-demo.selectorLabels" . | nindent 6 }} 12 | app.kubernetes.io/component: web 13 | template: 14 | metadata: 15 | labels: 16 | {{- include "rails-k8s-demo.selectorLabels" . | nindent 8 }} 17 | app.kubernetes.io/component: web 18 | spec: 19 | {{- with .Values.imagePullSecrets }} 20 | imagePullSecrets: 21 | {{- toYaml . | nindent 8 }} 22 | {{- end }} 23 | serviceAccountName: {{ include "rails-k8s-demo.serviceAccountName" . }} 24 | securityContext: 25 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 26 | containers: 27 | - name: {{ .Chart.Name }} 28 | securityContext: 29 | {{- toYaml .Values.securityContext | nindent 12 }} 30 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 31 | imagePullPolicy: {{ .Values.image.pullPolicy }} 32 | ports: 33 | - name: http 34 | containerPort: 3000 35 | protocol: TCP 36 | command: 37 | - rails 38 | args: 39 | - "server" 40 | - "-b" 41 | - "0.0.0.0" 42 | # TODO: Figure out how to query the actual pod 43 | # livenessProbe: 44 | # httpGet: 45 | # scheme: HTTPS 46 | # host: demo.lewagon.co 47 | # path: / 48 | # port: 443 49 | # timeoutSeconds: 5 50 | # failureThreshold: 3 51 | # periodSeconds: 5 52 | # readinessProbe: 53 | # httpGet: 54 | # scheme: HTTPS 55 | # host: demo.lewagon.co 56 | # path: / 57 | # port: 443 58 | # timeoutSeconds: 5 59 | # failureThreshold: 3 60 | # initialDelaySeconds: 30 61 | envFrom: 62 | - configMapRef: 63 | name: common-env 64 | {{if .Values.dbConnectionString }} 65 | env: 66 | - name: DATABASE_URL 67 | valueFrom: 68 | secretKeyRef: 69 | name: db-connection-string 70 | key: db_connection_string 71 | {{ end }} 72 | {{if .Values.railsMasterKey }} 73 | env: 74 | - name: RAILS_MASTER_KEY 75 | valueFrom: 76 | secretKeyRef: 77 | name: rails-master-key 78 | key: rails_master_key 79 | {{ end }} 80 | resources: 81 | {{- toYaml .Values.resources | nindent 12 }} 82 | {{- with .Values.nodeSelector }} 83 | nodeSelector: 84 | {{- toYaml . | nindent 8 }} 85 | {{- end }} 86 | {{- with .Values.affinity }} 87 | affinity: 88 | {{- toYaml . | nindent 8 }} 89 | {{- end }} 90 | {{- with .Values.tolerations }} 91 | tolerations: 92 | {{- toYaml . | nindent 8 }} 93 | {{- end }} 94 | -------------------------------------------------------------------------------- /helm/templates/deployments/sidekiq.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "rails-k8s-demo.fullname" . }}-sidekiq 5 | labels: 6 | {{ include "rails-k8s-demo.labels" . | indent 4 }} 7 | spec: 8 | replicas: {{ .Values.sidekiq.replicaCount }} 9 | selector: 10 | matchLabels: 11 | {{- include "rails-k8s-demo.selectorLabels" . | nindent 6 }} 12 | app.kubernetes.io/component: background 13 | template: 14 | metadata: 15 | labels: 16 | {{- include "rails-k8s-demo.selectorLabels" . | nindent 8 }} 17 | app.kubernetes.io/component: background 18 | spec: 19 | {{- with .Values.imagePullSecrets }} 20 | imagePullSecrets: 21 | {{- toYaml . | nindent 8 }} 22 | {{- end }} 23 | containers: 24 | - name: {{ .Chart.Name }} 25 | securityContext: 26 | {{- toYaml .Values.securityContext | nindent 12 }} 27 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 28 | imagePullPolicy: {{ .Values.image.pullPolicy }} 29 | ports: 30 | - name: sidekiqalive 31 | containerPort: 7433 32 | command: 33 | - bundle 34 | - exec 35 | - sidekiq 36 | args: 37 | - "-C" 38 | - "config/sidekiq.yml" 39 | livenessProbe: 40 | httpGet: 41 | path: / 42 | port: 7433 43 | initialDelaySeconds: 30 44 | readinessProbe: 45 | httpGet: 46 | path: / 47 | port: 7433 48 | initialDelaySeconds: 30 # app specific. Time your sidekiq takes to start processing. 49 | envFrom: 50 | - configMapRef: 51 | name: common-env 52 | {{if .Values.dbConnectionString }} 53 | env: 54 | - name: DATABASE_URL 55 | valueFrom: 56 | secretKeyRef: 57 | name: db-connection-string 58 | key: db_connection_string 59 | {{ end }} 60 | {{if .Values.railsMasterKey }} 61 | env: 62 | - name: RAILS_MASTER_KEY 63 | valueFrom: 64 | secretKeyRef: 65 | name: rails-master-key 66 | key: rails_master_key 67 | {{ end }} 68 | resources: 69 | {{- toYaml .Values.resources | nindent 12 }} 70 | # Dafault helm 71 | {{- with .Values.nodeSelector }} 72 | nodeSelector: 73 | {{- toYaml . | nindent 8 }} 74 | {{- end }} 75 | {{- with .Values.affinity }} 76 | affinity: 77 | {{- toYaml . | nindent 8 }} 78 | {{- end }} 79 | {{- with .Values.tolerations }} 80 | tolerations: 81 | {{- toYaml . | nindent 8 }} 82 | {{- end }} 83 | -------------------------------------------------------------------------------- /helm/templates/environment/common-env.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: common-env 5 | annotations: 6 | helm.sh/hook: pre-upgrade, pre-install 7 | helm.sh/hook-weight: "-1" 8 | data: 9 | REDIS_URL: redis://{{ .Release.Name }}-redis-master.default.svc.cluster.local 10 | {{- range $key, $value := .Values.commonEnv }} 11 | {{ $key }}: {{ $value | quote }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /helm/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "rails-k8s-demo.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 5 | apiVersion: networking.k8s.io/v1beta1 6 | {{- else -}} 7 | apiVersion: extensions/v1beta1 8 | {{- end }} 9 | kind: Ingress 10 | metadata: 11 | name: {{ $fullName }} 12 | labels: 13 | {{- include "rails-k8s-demo.labels" . | nindent 4 }} 14 | {{- with .Values.ingress.annotations }} 15 | annotations: 16 | {{- toYaml . | nindent 4 }} 17 | {{- end }} 18 | spec: 19 | {{- if .Values.ingress.tls }} 20 | tls: 21 | {{- range .Values.ingress.tls }} 22 | - hosts: 23 | {{- range .hosts }} 24 | - {{ . | quote }} 25 | {{- end }} 26 | secretName: {{ .secretName }} 27 | {{- end }} 28 | {{- end }} 29 | rules: 30 | {{- range .Values.ingress.hosts }} 31 | - host: {{ .host | quote }} 32 | http: 33 | paths: 34 | {{- range .paths }} 35 | - path: {{ . }} 36 | backend: 37 | serviceName: {{ $fullName }} 38 | servicePort: {{ $svcPort }} 39 | {{- end }} 40 | {{- end }} 41 | {{- end }} 42 | -------------------------------------------------------------------------------- /helm/templates/jobs/db-prepare.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: db-prepare 5 | annotations: 6 | helm.sh/hook: pre-upgrade, pre-install 7 | helm.sh/hook-delete-policy: hook-succeeded, hook-failed 8 | spec: 9 | activeDeadlineSeconds: 180 10 | backoffLimit: 3 11 | template: 12 | spec: 13 | restartPolicy: Never 14 | containers: 15 | - name: db-prepare 16 | image: {{ .Values.image.repository }}:{{ .Values.image.tag }} 17 | imagePullPolicy: {{ .Values.image.pullPolicy }} 18 | command: 19 | - bundle 20 | - exec 21 | - rails 22 | - db:prepare 23 | {{if .Values.dbConnectionString }} 24 | env: 25 | - name: DATABASE_URL 26 | valueFrom: 27 | secretKeyRef: 28 | name: db-connection-string 29 | key: db_connection_string 30 | {{ end }} 31 | -------------------------------------------------------------------------------- /helm/templates/secrets/db-connection-secret.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.dbConnectionString }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: db-connection-string 6 | annotations: 7 | helm.sh/hook: pre-upgrade, pre-install 8 | helm.sh/hook-weight: "-2" 9 | type: Opaque 10 | stringData: 11 | db_connection_string: {{ .Values.dbConnectionString }} 12 | {{ end }} 13 | -------------------------------------------------------------------------------- /helm/templates/secrets/master-key-secret.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.railsMasterKey }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: rails-master-key 6 | annotations: 7 | helm.sh/hook: pre-upgrade, pre-install 8 | helm.sh/hook-weight: "-2" 9 | type: Opaque 10 | stringData: 11 | rails_master_key: {{ .Values.railsMasterKey }} 12 | {{ end }} 13 | -------------------------------------------------------------------------------- /helm/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "rails-k8s-demo.fullname" . }} 5 | labels: {{- include "rails-k8s-demo.labels" . | nindent 4 }} 6 | spec: 7 | type: {{ .Values.service.type }} 8 | ports: 9 | - port: {{ .Values.service.port }} 10 | targetPort: 3000 11 | protocol: TCP 12 | name: http 13 | selector: 14 | app.kubernetes.io/name: {{ include "rails-k8s-demo.name" . }} 15 | app.kubernetes.io/instance: {{ .Release.Name }} 16 | app.kubernetes.io/component: web 17 | -------------------------------------------------------------------------------- /helm/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "rails-k8s-demo.serviceAccountName" . }} 6 | labels: 7 | {{ include "rails-k8s-demo.labels" . | nindent 4 }} 8 | {{- end -}} 9 | -------------------------------------------------------------------------------- /helm/templates/ssl/cert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1alpha2 2 | kind: Certificate 3 | metadata: 4 | name: tls-cert 5 | spec: 6 | secretName: tls-cert 7 | 8 | dnsNames: 9 | {{- range .Values.ingress.hosts }} 10 | - {{ .host | quote }} 11 | {{- end }} 12 | 13 | issuerRef: 14 | name: letsencrypt-prod 15 | kind: ClusterIssuer 16 | group: cert-manager.io 17 | -------------------------------------------------------------------------------- /helm/templates/ssl/production-issuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1alpha2 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt-prod 5 | spec: 6 | acme: 7 | # Email address used for ACME registration 8 | email: andrey@lewagon.org # TODO: move to values 9 | server: https://acme-v02.api.letsencrypt.org/directory 10 | privateKeySecretRef: 11 | # Name of a secret used to store the ACME account private key 12 | name: letsencrypt-prod 13 | # Add a single challenge solver, HTTP01 using nginx 14 | solvers: 15 | - http01: 16 | ingress: 17 | class: nginx 18 | -------------------------------------------------------------------------------- /helm/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "rails-k8s-demo.fullname" . }}-test-connection" 5 | labels: 6 | {{ include "rails-k8s-demo.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test-success 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "rails-k8s-demo.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /helm/values.yaml: -------------------------------------------------------------------------------- 1 | image: 2 | repository: quay.io/lewagon/rails-k8s-demo 3 | tag: latest 4 | pullPolicy: Always # TODO: change to IfNotPresent after debug? 5 | 6 | imagePullSecrets: [] 7 | nameOverride: "" 8 | fullnameOverride: "" 9 | 10 | rails: 11 | replicaCount: 1 12 | 13 | sidekiq: 14 | replicaCount: 1 15 | 16 | # ENV variables declared here will be set for both Rails and Sidekiq pods 17 | commonEnv: 18 | # ENV_VAR: value 19 | 20 | # Values used to generate secrets at chart install/upgrade. 21 | # These values need to be injected through shell env/CI secrets, 22 | # Don't set them here and don't commit to source control. 23 | dbConnectionString: 24 | railsMasterKey: 25 | 26 | serviceAccount: 27 | # Specifies whether a service account should be created 28 | create: true 29 | # The name of the service account to use. 30 | # If not set and create is true, a name is generated using the fullname template 31 | name: 32 | 33 | podSecurityContext: 34 | {} 35 | # fsGroup: 2000 36 | 37 | securityContext: 38 | {} 39 | # capabilities: 40 | # drop: 41 | # - ALL 42 | # readOnlyRootFilesystem: true 43 | # runAsNonRoot: true 44 | # runAsUser: 1000 45 | 46 | service: 47 | type: ClusterIP 48 | port: 80 49 | 50 | ingress: 51 | enabled: true 52 | annotations: 53 | kubernetes.io/ingress.class: nginx 54 | certmanager.io/cluster-issuer: letsencrypt-prod 55 | hosts: 56 | - host: demo.lewagon.co 57 | paths: ["/"] 58 | 59 | tls: 60 | - secretName: tls-cert 61 | hosts: 62 | - demo.lewagon.co 63 | 64 | resources: 65 | {} 66 | # We usually recommend not to specify default resources and to leave this as a conscious 67 | # choice for the user. This also increases chances charts run on environments with little 68 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 69 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 70 | # limits: 71 | # cpu: 100m 72 | # memory: 128Mi 73 | # requests: 74 | # cpu: 100m 75 | # memory: 128Mi 76 | 77 | nodeSelector: {} 78 | 79 | tolerations: [] 80 | 81 | affinity: {} 82 | 83 | redis: 84 | cluster: 85 | enabled: false 86 | usePassword: false 87 | # By default, FLUSH and FLUSHALL are disabled in bitnami/redis chart 88 | master: 89 | disableCommands: [] 90 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "review_scraper", 3 | "private": true, 4 | "dependencies": { 5 | "@rails/actioncable": "^6.0.0-alpha", 6 | "@rails/ujs": "^6.0.0-alpha", 7 | "@rails/webpacker": "^4.0.7", 8 | "bootstrap": "4.3.1", 9 | "dot-prop": "5.1.1", 10 | "jquery": "^3.5.0", 11 | "minimist": "^1.2.3", 12 | "popper.js": "^1.16.0", 13 | "stimulus": "^1.1.1", 14 | "turbolinks": "^5.2.0" 15 | }, 16 | "version": "0.1.0", 17 | "devDependencies": { 18 | "webpack-dev-server": "^3.9.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import'), 4 | require('postcss-flexbugs-fixes'), 5 | require('postcss-preset-env')({ 6 | autoprefixer: { 7 | flexbox: 'no-2009' 8 | }, 9 | stage: 3 10 | }) 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 5 | end 6 | -------------------------------------------------------------------------------- /test/channels/application_cable/connection_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase 4 | # test "connects with cookies" do 5 | # cookies.signed[:user_id] = 42 6 | # 7 | # connect 8 | # 9 | # assert_equal connection.user_id, "42" 10 | # end 11 | end 12 | -------------------------------------------------------------------------------- /test/channels/messages_channel_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class MessagesChannelTest < ActionCable::Channel::TestCase 4 | # test "subscribes" do 5 | # subscribe 6 | # assert subscription.confirmed? 7 | # end 8 | end 9 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/logins_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class LoginControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/messages_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class MessagesControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/test/fixtures/.keep -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/test/helpers/.keep -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/test/models/.keep -------------------------------------------------------------------------------- /test/models/message_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class MessageTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/preview_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class PreviewTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/test/system/.keep -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require_relative "../config/environment" 3 | require "rails/test_help" 4 | 5 | class ActiveSupport::TestCase 6 | # Run tests in parallel with specified workers 7 | parallelize(workers: :number_of_processors) 8 | 9 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 10 | fixtures :all 11 | 12 | # Add more helper methods to be used by all tests here... 13 | end 14 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/tmp/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lewagon/rails-k8s-demo/e2f5cf697d86811b52cb0b9045d37cbe0d02dda6/vendor/.keep --------------------------------------------------------------------------------