diff --git a/.controlplane/Dockerfile b/.controlplane/Dockerfile index 3e9f83f6..103f8a4b 100644 --- a/.controlplane/Dockerfile +++ b/.controlplane/Dockerfile @@ -17,18 +17,10 @@ RUN bundle config set without 'development test' && \ bundle install --jobs=3 --retry=3 # install node packages -COPY package.json yarn.lock . +COPY package.json yarn.lock ./ RUN yarn install -# pick necessary app files -COPY Gemfile* config.ru Rakefile babel.config.js ./ -COPY app ./app -COPY bin ./bin -COPY client ./client -COPY config ./config -COPY db ./db -COPY lib ./lib -COPY public ./public +COPY . ./ ENV RAILS_ENV=production ENV NODE_ENV=production @@ -36,6 +28,7 @@ ENV NODE_ENV=production # compiling assets requires any value for ENV of SECRET_KEY_BASE ENV SECRET_KEY_BASE=NOT_USED_NON_BLANK +RUN rails react_on_rails:locale RUN rails assets:precompile # add entrypoint diff --git a/.controlplane/controlplane.yml b/.controlplane/controlplane.yml index 8544025c..b72bae2a 100644 --- a/.controlplane/controlplane.yml +++ b/.controlplane/controlplane.yml @@ -1,10 +1,12 @@ # Configuration for "Heroku to CPLN playbook" custom scripts aliases: common: &common - # Change this to your org name for staging. Production apps will use a different org - # for security. - cpln_org: shakacode-demo - # Change `shakacode-staging` to your-org-name-for-staging + # Org for staging and QA apps is typically set as an alias + # Production apps will use a different org than staging for security. + # Change this value to your org name + # or set ENV value to CPLN_ORG as that will override whatever is used here for all cpl commands + # cpln_org: shakacode-open-source-examples + # Example apps use only location. CPLN offers the ability to use multiple locations. default_location: aws-us-east-2 # Configure the workload name used as a template for one-off scripts, like a Heroku one-off dyno. @@ -13,10 +15,12 @@ aliases: # and the application image updates. app_workloads: - rails + - daily-task # Like Heroku add-ons additional_workloads: - redis - postgres + - daily-task apps: react-webpack-rails-tutorial: diff --git a/.controlplane/readme.md b/.controlplane/readme.md index 08414374..76bca72d 100644 --- a/.controlplane/readme.md +++ b/.controlplane/readme.md @@ -11,42 +11,63 @@ You can see the definition of Postgres and Redis in the `.controlplane/templates ## Prerequisites 1. Ensure your [Control Plane](https://controlplane.com) account is set up. +You should have an `organization` `` for testing in that account. +You will modify value for `aliases.common.cpln_org` in `.controlplane/controlplane.yml`. +If you need an organization, please [contact Shakacode](mailto:controlplane@shakacode.com). -2. Set up an `organization` for testing in that account and modify `aliases.common.cpln_org` in `.controlplane/controlplane.yml` . +2. Run `cpln image docker-login --org ` to ensure that you have access to the Control Plane Docker registry. -3. Install Control Plane CLI (and configure access) [docs here](https://docs.controlplane.com/quickstart/quick-start-3-cli#getting-started-with-the-cli). You can update the `cpln` command line with the same command as installation, `npm install -g @controlplane/cli`. Then run `cpln login` to ensure access. +3. Install Control Plane CLI (and configure access) using `npm install -g @controlplane/cli`. +You can update the `cpln` command line with `npm update -g @controlplane/cli`. +Then run `cpln login` to ensure access. +For more informatation check out the +[docs here](https://docs.controlplane.com/quickstart/quick-start-3-cli#getting-started-with-the-cli). -4. Install [Heroku to Control Plane](https://github.com/shakacode/heroku-to-control-plane) playbook CLI [`cpl` gem](https://rubygems.org/gems/cpl) on your project's Gemfile or globally. +4. Install the latest version of +[`cpl` gem](https://rubygems.org/gems/cpl) +on your project's Gemfile or globally. +For more information check out +[Heroku to Control Plane](https://github.com/shakacode/heroku-to-control-plane). -5. This project has a `Dockerfile` for Control Plane in this directory. You can use it as an example for your project. Ensure that you have Docker running. +5. This project has a `Dockerfile` for Control Plane in `.controlplane` directory. +You can use it as an example for your project. +Ensure that you have Docker running. -## Tips -Do not confuse the `cpl` CLI with the `cpln` CLI. The `cpl` CLI is the Heroku to Control Plane playbook CLI. The `cpln` CLI is the Control Plane CLI. +### Tips +Do not confuse the `cpl` CLI with the `cpln` CLI. +The `cpl` CLI is the Heroku to Control Plane playbook CLI. +The `cpln` CLI is the Control Plane CLI. ## Project Configuration See the filese in the `./controlplane` directory. 1. `/templates`: defines the objects created with the `cpl setup` command. -2. `/controlplane.yml`: defines the organization, location, and app name. +These YAML files are the same as used by the `cpln apply` command. +2. `/controlplane.yml`: defines your application, including the organization, location, and app name. 3. `Dockerfile`: defines the Docker image used to run the app on Control Plane. 4. `entrypoint.sh`: defines the entrypoint script used to run the app on Control Plane. ## Setup and run -Check if the Control Plane organization and location are correct in `.controlplane/controlplane.yml`. You should be able to see this information in the Control Plane UI. +Check if the Control Plane organization and location are correct in `.controlplane/controlplane.yml`. +You should be able to see this information in the Control Plane UI. -```sh -# Note, below commands use `cpl` which is the Heroku to Control Plane playbook script. +**Note:** The below commands use `cpl` which is the Heroku to Control Plane playbook gem, +and not `cpln` which is the Control Plane CLI. +```sh # Provision all infrastructure on Control Plane. # app tutorial-app will be created per definition in .controlplane/controlplane.yml -cpl setup gvc postgres redis rails -a tutorial-app +cpl apply-template gvc postgres redis rails -a tutorial-app # Build and push docker image to Control Plane repository # Note, may take many minutes. Be patient. +# Check for error messages, such as forgetting to run `cpln image docker-login --org ` cpl build-image -a tutorial-app # Promote image to app after running `cpl build-image command` +# Note, the UX of images may not show the image for up to 5 minutes. +# However, it's ready. cpl deploy-image -a tutorial-app # See how app is starting up @@ -56,25 +77,30 @@ cpl logs -a tutorial-app cpl open -a tutorial-app ``` -## Promoting code upgrades +### Promoting code updates + +After committing code, you will update your deployment of `tutorial-app` with the following commands: ```sh -# Build and push new image with sequential image tagging, e.g. 'ror-tutorial_123' +# Build and push new image with sequential image tagging, e.g. 'tutorial-app:1', then 'tutorial-app:2', etc. cpl build-image -a tutorial-app -# OR -# Build and push with sequential image tagging and commit SHA, e.g. 'ror-tutorial_123_ABCD' -cpl build-image -a tutorial-app --commit ABCD - # Run database migrations (or other release tasks) with latest image, # while app is still running on previous image. # This is analogous to the release phase. cpl runner rails db:migrate -a tutorial-app --image latest -# Pomote latest image to app +# Pomote latest image to app after migrations run cpl deploy-image -a tutorial-app ``` +If you needed to push a new image with a specific commit SHA, you can run the following command: + +```sh +# Build and push with sequential image tagging and commit SHA, e.g. 'tutorial-app:123_ABCD' +cpl build-image -a tutorial-app --commit ABCD +``` + ## Other notes ### `entrypoint.sh` diff --git a/.controlplane/templates/daily-task.yml b/.controlplane/templates/daily-task.yml new file mode 100644 index 00000000..6981aeac --- /dev/null +++ b/.controlplane/templates/daily-task.yml @@ -0,0 +1,36 @@ +kind: workload +name: daily-task +spec: + # https://docs.controlplane.com/reference/workload#cron-configuration + type: cron + job: + activeDeadlineSeconds: 3600 + concurrencyPolicy: Forbid + historyLimit: 5 + restartPolicy: Never + # daily. See cron docs + schedule: 0 0 * * * + containers: + - name: daily-task + cpu: 50m + memory: 256Mi + args: + - bundle + - exec + - rails + - db:reset + env: + - name: DISABLE_DATABASE_ENVIRONMENT_CHECK #To allow db:reset in production environment + value: "1" + inheritEnv: true + image: "/org/APP_ORG/image/APP_IMAGE" + defaultOptions: + autoscaling: + minScale: 1 + maxScale: 1 + capacityAI: false + firewallConfig: + external: + outboundAllowCIDR: + - 0.0.0.0/0 + identityLink: /org/APP_ORG/gvc/APP_GVC/identity/postgres-poc-identity diff --git a/.controlplane/templates/gvc.yml b/.controlplane/templates/gvc.yml index ce9cc03b..93028630 100644 --- a/.controlplane/templates/gvc.yml +++ b/.controlplane/templates/gvc.yml @@ -9,7 +9,7 @@ spec: # Password does not matter because host postgres.APP_GVC.cpln.local can only be accessed # locally within CPLN GVC, and postgres running on a CPLN workload is something only for a # test app that lacks persistence. - value: 'postgres://postgres:password123@postgres.APP_GVC.cpln.local:5432/APP_GVC' + value: 'postgres://the_user:the_password@postgres.APP_GVC.cpln.local:5432/APP_GVC' - name: RAILS_ENV value: production - name: NODE_ENV diff --git a/.controlplane/templates/postgres.yml b/.controlplane/templates/postgres.yml index 47d59186..7291e37d 100644 --- a/.controlplane/templates/postgres.yml +++ b/.controlplane/templates/postgres.yml @@ -1,33 +1,176 @@ -# Template setup of Postgres workload, roughly corresponding to a Heroku add-on for a database +# Comes from example at +# https://github.com/controlplane-com/examples/blob/main/examples/postgres/manifest.yaml + +kind: volumeset +name: postgres-poc-vs +description: postgres-poc-vs +spec: + autoscaling: + maxCapacity: 1000 + minFreePercentage: 1 + scalingFactor: 1.1 + fileSystemType: ext4 + initialCapacity: 10 + performanceClass: general-purpose-ssd + snapshots: + createFinalSnapshot: true + retentionDuration: 7d + +--- +kind: secret +name: postgres-poc-credentials +description: '' +type: dictionary +data: + password: the_password #Replace this with a real password + username: the_user #Replace this with a real username + +--- +kind: secret +name: postgres-poc-entrypoint-script +type: opaque +data: + encoding: base64 + payload: >- + IyEvdXNyL2Jpbi9lbnYgYmFzaAoKc291cmNlIC91c3IvbG9jYWwvYmluL2RvY2tlci1lbnRyeXBvaW50LnNoCgppbnN0YWxsX2RlcHMoKSB7CiAgYXB0LWdldCB1cGRhdGUgLXkgPiAvZGV2L251bGwKICBhcHQtZ2V0IGluc3RhbGwgY3VybCAteSA+IC9kZXYvbnVsbAogIGFwdC1nZXQgaW5zdGFsbCB1bnppcCAteSA+IC9kZXYvbnVsbAogIGN1cmwgImh0dHBzOi8vYXdzY2xpLmFtYXpvbmF3cy5jb20vYXdzY2xpLWV4ZS1saW51eC14ODZfNjQuemlwIiAtbyAiYXdzY2xpdjIuemlwIiA+IC9kZXYvbnVsbAogIHVuemlwIGF3c2NsaXYyLnppcCA+IC9kZXYvbnVsbAogIC4vYXdzL2luc3RhbGwgPiAvZGV2L251bGwKfQoKZGJfaGFzX2JlZW5fcmVzdG9yZWQoKSB7CiAgaWYgWyAhIC1mICIkUEdEQVRBL0NQTE5fUkVTVE9SRUQiIF07IHRoZW4KICAgIHJldHVybiAxCiAgZmkKCiAgaWYgISBncmVwIC1xICJcLT4gJDEkIiAiJFBHREFUQS9DUExOX1JFU1RPUkVEIjsgdGhlbgogICAgcmV0dXJuIDEKICBlbHNlCiAgICByZXR1cm4gMAogIGZpCn0KCnJlc3RvcmVfZGIoKSB7Cgl3aGlsZSBbICEgLVMgL3Zhci9ydW4vcG9zdGdyZXNxbC8ucy5QR1NRTC41NDMyIF0KCWRvCiAgICBlY2hvICJXYWl0aW5nIDVzIGZvciBkYiBzb2NrZXQgdG8gYmUgYXZhaWxhYmxlIgogICAgc2xlZXAgNXMKICBkb25lCgoKCWlmICEgZGJfaGFzX2JlZW5fcmVzdG9yZWQgIiQxIjsgdGhlbgoJICBlY2hvICJJdCBhcHBlYXJzIGRiICckMScgaGFzIG5vdCB5ZXQgYmVlbiByZXN0b3JlZCBmcm9tIFMzLiBBdHRlbXB0aW5nIHRvIHJlc3RvcmUgJDEgZnJvbSAkMiIKCSAgaW5zdGFsbF9kZXBzCgkgIGRvY2tlcl9zZXR1cF9kYiAjRW5zdXJlcyAkUE9TVEdSRVNfREIgZXhpc3RzIChkZWZpbmVkIGluIHRoZSBlbnRyeXBvaW50IHNjcmlwdCBmcm9tIHRoZSBwb3N0Z3JlcyBkb2NrZXIgaW1hZ2UpCgkgIGF3cyBzMyBjcCAiJDIiIC0gfCBwZ19yZXN0b3JlIC0tY2xlYW4gLS1uby1hY2wgLS1uby1vd25lciAtZCAiJDEiIC1VICIkUE9TVEdSRVNfVVNFUiIKCSAgZWNobyAiJChkYXRlKTogJDIgLT4gJDEiIHwgY2F0ID4+ICIkUEdEQVRBL0NQTE5fUkVTVE9SRUQiCgllbHNlCgkgIGVjaG8gIkRiICckMScgYWxyZWFkeSBleGlzdHMuIFJlYWR5ISIKICBmaQp9CgpfbWFpbiAiJEAiICYKYmFja2dyb3VuZFByb2Nlc3M9JCEKCmlmIFsgLW4gIiRQT1NUR1JFU19BUkNISVZFX1VSSSIgXTsgdGhlbgogIHJlc3RvcmVfZGIgIiRQT1NUR1JFU19EQiIgIiRQT1NUR1JFU19BUkNISVZFX1VSSSIKZWxzZQogIGVjaG8gIkRlY2xpbmluZyB0byByZXN0b3JlIHRoZSBkYiBiZWNhdXNlIG5vIGFyY2hpdmUgdXJpIHdhcyBwcm92aWRlZCIKZmkKCndhaXQgJGJhY2tncm91bmRQcm9jZXNzCgoK + +#Here is the ASCII-encoded version of the script in the secret above +#!/usr/bin/env bash +# +#source /usr/local/bin/docker-entrypoint.sh +# +#install_deps() { +# apt-get update -y > /dev/null +# apt-get install curl -y > /dev/null +# apt-get install unzip -y > /dev/null +# curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" > /dev/null +# unzip awscliv2.zip > /dev/null +# ./aws/install > /dev/null +#} +# +#db_has_been_restored() { +# if [ ! -f "$PGDATA/CPLN_RESTORED" ]; then +# return 1 +# fi +# +# if ! grep -q "\-> $1$" "$PGDATA/CPLN_RESTORED"; then +# return 1 +# else +# return 0 +# fi +#} +# +#restore_db() { +# while [ ! -S /var/run/postgresql/.s.PGSQL.5432 ] +# do +# echo "Waiting 5s for db socket to be available" +# sleep 5s +# done +# +# +# if ! db_has_been_restored "$1"; then +# echo "It appears db '$1' has not yet been restored from S3. Attempting to restore $1 from $2" +# install_deps +# docker_setup_db #Ensures $POSTGRES_DB exists (defined in the entrypoint script from the postgres docker image) +# aws s3 cp "$2" - | pg_restore --clean --no-acl --no-owner -d "$1" -U "$POSTGRES_USER" +# echo "$(date): $2 -> $1" | cat >> "$PGDATA/CPLN_RESTORED" +# else +# echo "Db '$1' already exists. Ready!" +# fi +#} +# +#_main "$@" & +#backgroundProcess=$! +# +#if [ -n "$POSTGRES_ARCHIVE_URI" ]; then +# restore_db "$POSTGRES_DB" "$POSTGRES_ARCHIVE_URI" +#else +# echo "Declining to restore the db because no archive uri was provided" +#fi +# +#wait $backgroundProcess + +--- +kind: identity +name: postgres-poc-identity +description: postgres-poc-identity + +--- +kind: policy +name: postgres-poc-access +description: postgres-poc-access +bindings: + - permissions: + - reveal +# Uncomment these two +# - use +# - view + principalLinks: + - //gvc/APP_GVC/identity/postgres-poc-identity +targetKind: secret +targetLinks: + - //secret/postgres-poc-credentials + - //secret/postgres-poc-entrypoint-script + +--- kind: workload name: postgres +description: postgres spec: - type: standard + type: stateful containers: - - name: postgres + - cpu: 1000m + memory: 512Mi env: - - name: PGUSER - value: postgres - # Hardcoded password. See firewall comment below. - - name: POSTGRES_PASSWORD - value: password123 - - name: POSTGRES_USER - value: postgres - image: 'postgres:13.8-alpine' + # Uncomment next two envs will cause the db to be restored from the archive uri + # - name: POSTGRES_ARCHIVE_URI #Use this var to control the automatic restore behavior. If you leave it out, the db will start empty. + # value: s3://YOUR_BUCKET/PATH_TO_ARCHIVE_FILE + # - name: POSTGRES_DB #The name of the initial db in case of doing a restore + # value: test + - name: PGDATA #The location postgres stores the db. This can be anything other than /var/lib/postgresql/data, but it must be inside the mount point for the volume set + value: "/var/lib/postgresql/data/pg_data" + - name: POSTGRES_PASSWORD #The password for the default user + value: cpln://secret/postgres-poc-credentials.password + - name: POSTGRES_USER #The name of the default user + value: cpln://secret/postgres-poc-credentials.username + name: stateful + image: postgres:15 + command: /bin/bash + args: + - "-c" + - "cat /usr/local/bin/cpln-entrypoint.sh >> ./cpln-entrypoint.sh && chmod u+x ./cpln-entrypoint.sh && ./cpln-entrypoint.sh postgres" + #command: "cpln-entrypoint.sh" + #args: + # - "postgres" ports: - number: 5432 protocol: tcp volumes: - - path: /var/lib/postgresql/data - recoveryPolicy: retain - uri: 'scratch://postgres-vol' - # Important that postgres does not scaling because disk storage is local to one server! + - uri: cpln://volumeset/postgres-poc-vs + path: "/var/lib/postgresql/data" + # Make the ENV value for the entry script a file + - uri: cpln://secret/postgres-poc-entrypoint-script + path: "/usr/local/bin/cpln-entrypoint.sh" + inheritEnv: false + livenessProbe: + tcpSocket: + port: 5432 + failureThreshold: 1 + readinessProbe: + tcpSocket: + port: 5432 + failureThreshold: 1 + identityLink: //identity/postgres-poc-identity defaultOptions: + capacityAI: false autoscaling: + metric: cpu + target: 95 maxScale: 1 - capacityAI: false - # This firewall configuration corresponds to using a simple, hard-coded password for postgres - # in the gvc.yml template. firewallConfig: + external: + inboundAllowCIDR: [] + outboundAllowCIDR: + - 0.0.0.0/0 internal: inboundAllowType: same-gvc diff --git a/.controlplane/templates/redis.yml b/.controlplane/templates/redis.yml index 5d24b2a0..124e665a 100644 --- a/.controlplane/templates/redis.yml +++ b/.controlplane/templates/redis.yml @@ -4,7 +4,7 @@ spec: type: standard containers: - name: redis - image: 'redis:6.2.6' + image: 'redis:6.2-alpine' ports: - number: 6379 protocol: tcp diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..d28956cd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,51 @@ +# From .gitignore + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/*.log +/tmp +/public/assets +.env +node_modules +npm-debug.log* +/coverage +dump.rdb +.DS_Store + +# Ignore bundle dependencies +vendor/ruby + +# RVM gemset +.ruby-gemset + +# Generated js bundles +/public/packs +/public/packs-test + +# Rubymine/IntelliJ +.idea + +# Redis generated file +dump.rdb + +# Ignore i18n-js +client/app/libs/i18n/translations.js +client/app/libs/i18n/default.js + +/yarn-error.log +yarn-debug.log* +.yarn-integrity + +################################################### +# Specific to .dockerignore +.git/ +.github/ +spec/ +scripts/ + diff --git a/Gemfile b/Gemfile index f9f2552e..7c9b8883 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,8 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.1.2" -gem "cpl", "~> 0.3.3" +gem "cpl", "~>1.1.2" + gem "react_on_rails", "13.2.0" gem "shakapacker", "7.0.3" diff --git a/Gemfile.lock b/Gemfile.lock index 48e69689..d7457586 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -116,7 +116,7 @@ GEM term-ansicolor (~> 1.6) thor (>= 0.20.3, < 2.0) tins (~> 1.16) - cpl (0.3.3) + cpl (1.1.2) debug (~> 1.7.1) dotenv (~> 2.8.1) psych (~> 5.1.0) @@ -157,8 +157,9 @@ GEM concurrent-ruby (~> 1.0) interception (0.5) io-console (0.6.0) - irb (1.7.4) - reline (>= 0.3.6) + irb (1.8.3) + rdoc + reline (>= 0.3.8) jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) @@ -222,7 +223,7 @@ GEM pry-stack_explorer (0.6.1) binding_of_caller (~> 1.0) pry (~> 0.13) - psych (5.1.0) + psych (5.1.1.1) stringio public_suffix (5.0.3) puma (6.3.1) @@ -291,7 +292,7 @@ GEM redcarpet (3.6.0) redis (4.8.1) regexp_parser (2.8.1) - reline (0.3.8) + reline (0.3.9) io-console (~> 0.5) require_all (3.0.0) rexml (3.2.6) @@ -428,7 +429,7 @@ DEPENDENCIES capybara-screenshot coffee-rails coveralls_reborn (~> 0.25.0) - cpl (~> 0.3.3) + cpl (~> 1.1.2) database_cleaner debug (>= 1.0.0) factory_bot_rails diff --git a/client/app/bundles/comments/components/CommentBox/CommentBox.module.scss b/client/app/bundles/comments/components/CommentBox/CommentBox.module.scss index 5c94b4ae..60cb622d 100644 --- a/client/app/bundles/comments/components/CommentBox/CommentBox.module.scss +++ b/client/app/bundles/comments/components/CommentBox/CommentBox.module.scss @@ -17,15 +17,3 @@ transition: opacity $animation-duration ease-in; } } - -.anchorButton { - background-color: transparent; - border: 0; - color: #337AB7; - cursor: pointer; - padding: 0; -} - -.anchorButton:hover { - text-decoration: underline; -}