├── .gitignore ├── Dockerfile.prod.with.fixes ├── README.md ├── deploy ├── cache_store.yml ├── cassandra.yml ├── consul.yml ├── database.yml ├── delayed_jobs.yml ├── development-local.rb ├── domain.yml ├── dynamic_settings.yml ├── outgoing_mail.yml ├── redis.yml ├── security.yml ├── selenium.yml └── vault.yml ├── docker-compose.override.skip-dory-example.yml ├── docker-compose.yml ├── override ├── 20210201170030_fill_lookup_uuid_and_resource_link_uuid_columns_at_lti_resource_links.rb ├── fill_custom_claim_columns_for_resource_link.rb └── fill_lookup_uuid_and_resource_link_uuid_columns.rb └── wait-for-it.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .data -------------------------------------------------------------------------------- /Dockerfile.prod.with.fixes: -------------------------------------------------------------------------------- 1 | # GENERATED FILE, DO NOT MODIFY! 2 | # To update this file please edit the relevant template and run the generation 3 | # task `build/dockerfile_writer.rb --env development --compose-file docker-compose.yml,docker-compose.override.yml --in build/Dockerfile.template --out Dockerfile` 4 | 5 | ARG RUBY=2.6-p6.0.4 6 | 7 | FROM instructure/ruby-passenger:$RUBY 8 | LABEL maintainer="Instructure" 9 | 10 | ARG POSTGRES_CLIENT=12 11 | ENV APP_HOME /usr/src/app/ 12 | ENV RAILS_ENV production 13 | ENV NGINX_MAX_UPLOAD_SIZE 10g 14 | ENV LANG en_US.UTF-8 15 | ENV LANGUAGE en_US.UTF-8 16 | ENV LC_CTYPE en_US.UTF-8 17 | ENV LC_ALL en_US.UTF-8 18 | ARG CANVAS_RAILS6_0=1 19 | ENV CANVAS_RAILS6_0=${CANVAS_RAILS6_0} 20 | 21 | ENV YARN_VERSION 1.19.1-1 22 | ENV BUNDLER_VERSION 2.2.17 23 | ENV GEM_HOME /home/docker/.gem/$RUBY 24 | ENV PATH $GEM_HOME/bin:$PATH 25 | ENV BUNDLE_APP_CONFIG /home/docker/.bundle 26 | 27 | WORKDIR $APP_HOME 28 | 29 | USER root 30 | 31 | ARG USER_ID 32 | # This step allows docker to write files to a host-mounted volume with the correct user permissions. 33 | # Without it, some linux distributions are unable to write at all to the host mounted volume. 34 | RUN if [ -n "$USER_ID" ]; then usermod -u "${USER_ID}" docker \ 35 | && chown --from=9999 docker /usr/src/nginx /usr/src/app -R; fi 36 | 37 | RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - \ 38 | && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ 39 | && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ 40 | && printf 'path-exclude /usr/share/doc/*\npath-exclude /usr/share/man/*' > /etc/dpkg/dpkg.cfg.d/01_nodoc \ 41 | && echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ 42 | && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \ 43 | && apt-get update -qq \ 44 | && apt-get install -qqy --no-install-recommends \ 45 | nodejs \ 46 | yarn="$YARN_VERSION" \ 47 | libxmlsec1-dev \ 48 | python-lxml \ 49 | libicu-dev \ 50 | parallel \ 51 | postgresql-client-$POSTGRES_CLIENT \ 52 | unzip \ 53 | pbzip2 \ 54 | fontforge \ 55 | autoconf \ 56 | automake \ 57 | && rm -rf /var/lib/apt/lists/* \ 58 | && mkdir -p /home/docker/.gem/ruby/$RUBY_MAJOR.0 59 | 60 | # install pulsar stuff 61 | ENV PULSAR_VERSION=2.6.1 62 | ENV PULSAR_CLIENT_SHA512=90fdb6e3ad85c9204f2b20a9077684f667f84be32df0952f8823ccee501c9d64a4c8131cab38a295a4cb66e2b63211afcc24f32130ded47e9da8f334ec6053f5 63 | ENV PULSAR_CLIENT_DEV_SHA512=d0cc58c0032cb35d4325769ab35018b5ed823bc9294d75edfb56e62a96861be4194d6546107af0d5f541a778cdc26274aac9cb7b5ced110521467f89696b2209 64 | RUN cd "$(mktemp -d)" && \ 65 | curl -SLO 'http://archive.apache.org/dist/pulsar/pulsar-'$PULSAR_VERSION'/DEB/apache-pulsar-client.deb' && \ 66 | curl -SLO 'http://archive.apache.org/dist/pulsar/pulsar-'$PULSAR_VERSION'/DEB/apache-pulsar-client-dev.deb' && \ 67 | echo $PULSAR_CLIENT_SHA512 '*apache-pulsar-client.deb' | shasum -a 512 -c -s - && \ 68 | echo $PULSAR_CLIENT_DEV_SHA512 '*apache-pulsar-client-dev.deb' | shasum -a 512 -c -s - && \ 69 | apt install ./apache-pulsar-client*.deb 70 | 71 | RUN if [ -e /var/lib/gems/$RUBY_MAJOR.0/gems/bundler-* ]; then BUNDLER_INSTALL="-i /var/lib/gems/$RUBY_MAJOR.0"; fi \ 72 | && gem uninstall --all --ignore-dependencies --force $BUNDLER_INSTALL bundler \ 73 | && gem install bundler --no-document -v $BUNDLER_VERSION \ 74 | && find $GEM_HOME ! -user docker | xargs chown docker:docker 75 | RUN npm install -g npm@latest && npm cache clean --force 76 | 77 | USER docker 78 | 79 | COPY --chown=docker:docker . $APP_HOME 80 | 81 | # if yarn hits a snag try one more time with concurrency set to 1 82 | # https://github.com/yarnpkg/yarn/issues/2629 83 | RUN bundle install --jobs 8 \ 84 | && yarn install --pure-lockfile --network-timeout 600000 --network-concurrency 1 \ 85 | && bundle lock --local --conservative 86 | 87 | # TODO: switch to canvas:compile_assets_dev once we stop using this Dockerfile in production/e2e 88 | RUN COMPILE_ASSETS_NPM_INSTALL=0 bundle exec rake canvas:compile_assets 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Docker Canvas for Integration Testing 2 | ------------------------------- 3 | 4 | Docker provisioning for Canvas integration tests (via LTI, etc) 5 | 6 | ## Prerequisites 7 | 8 | * [Docker Engine](https://docs.docker.com/engine/installation/) 9 | * [Docker Compose](https://docs.docker.com/compose/install/) 10 | * Large amount of memory allocated to your docker machine (Canvas uses a lot of memory). You need ~10GB to build the image and ~6GB to run the image. 11 | 12 | # Setting Up via Vendor Quick Start 13 | 14 | ## As of release/2022-09-14.120 15 | 16 | - Mutagen has disappeared, which probably led to the below permission issues 17 | - On Linux, you're likely to encounter permission errors during setup. You have two options: 18 | 1. Easiest fix is to comment out the `CANVAS_SKIP_DOCKER_USERMOD='true'` line in `script/docker_dev_setup.sh`. 19 | 2. Alternatively, you can follow the instructions in `doc/docker/README.md` before running setup. The `CANVAS_SKIP_DOCKER_USERMOD` option is set to true by default in `script/docker_dev_setup.sh`, so you don't need to set it manually. 20 | 21 | ## As of release/2022-06-08.121 22 | 23 | - Skipping Dory setup now works, see [Skip Dory](#skip-dory) for an alternative setup that still allows the use of the `canvas.docker` domain. 24 | - Mutagen and mutagen-compose are used for file synchronization between docker and host. 25 | - Skipping Mutagen results in the build failing later on at `bundle install` 26 | 27 | ## **Note: As of 2022-02-14 and release/2022-02-16.01** 28 | 29 | Using Canvas commit [`60fe0e86c40a346b6ff845e86b3156c7fbf5d32a`](https://github.com/instructure/canvas-lms/releases/tag/release%2F2022-02-16.01) 30 | 31 | Refer to the [Quick Start](https://github.com/instructure/canvas-lms/wiki/Quick-Start/3b9ade72fd0cb8a98fea2241c8b8f138bb4b8286) instructions. 32 | 33 | The `script/docker_dev_setup.sh` automated setup script is currently working 34 | on Debian 11 with the following notes: 35 | 36 | - [dory](https://github.com/FreedomBen/dory/tree/3420fddf14ae7cfa443612c4cc50d11073311649) appears to be mandatory. Install it on the host, not in a container. 37 | - To reset the state of the build, remember to delete both images and volumes. 38 | - Apply the following patch: 39 | 40 | ```diff 41 | commit a30dd213fee92f452160065f50ade61805d87330 (HEAD) 42 | Author: William Ono 43 | Date: Mon Feb 14 16:21:39 2022 -0800 44 | 45 | Account for different CLI syntax 46 | 47 | diff --git a/script/common/os/linux/impl.sh b/script/common/os/linux/impl.sh 48 | index 6223f790..fedf0430 100644 49 | --- a/script/common/os/linux/impl.sh 50 | +++ b/script/common/os/linux/impl.sh 51 | @@ -3,10 +3,10 @@ source script/common/utils/spinner.sh 52 | 53 | function set_service_util { 54 | if installed service; then 55 | - service_manager='service' 56 | + docker_status='service docker status' 57 | start_docker="sudo service docker start" 58 | elif installed systemctl; then 59 | - service_manager='systemctl' 60 | + docker_status='systemctl status docker' 61 | start_docker="sudo systemctl start docker" 62 | else 63 | echo "Unable to find 'service' or 'systemctl' installed." 64 | @@ -15,7 +15,7 @@ function set_service_util { 65 | } 66 | 67 | function start_docker_daemon { 68 | - eval "$service_manager docker status &> /dev/null" && return 0 69 | + eval "$docker_status &> /dev/null" && return 0 70 | prompt 'The docker daemon is not running. Start it? [y/n]' confirm 71 | [[ ${confirm:-n} == 'y' ]] || return 1 72 | eval "$start_docker" 73 | ``` 74 | 75 | ## Skip Dory 76 | 77 | This is using the same method laid out in [Communicating between projects](#communicating-between-projects). Where we put Canvas on a docker network named `docker_canvas_bridge` with an alias of `canvas.docker`. Containers that need to talk to Canvas directly, e.g.: for LTI 1.3 service calls, can reach Canvas via the `canvas.docker` domain as long as they're added to the `docker_canvas_bridge` network too. An example docker-compose.override.yml file with this implemented is in this repo as `docker-compose.override.skip-dory-example.yml`. 78 | 79 | In the example, there's an additional complication, as we're exposing Canvas externally on port 9100 instead of the default port 80. Containers within the same network don't use the external port and needs to talk to the internal port. To maintain consistency, it would be ideal if we can reach Canvas internally on port 9100 too. To this end, the example uses the `socat` utility to internally port forward 9100 to the Canvas web service on port 80. It is to the `socat` service that we attach the `docker_canvas_bridge` network to, since it is the one that'll receive all the requests on port 9100. 80 | 81 | --- 82 | --- 83 | 84 | # Old Non-Vendor Method 85 | 86 | Retained for reference. 87 | 88 | ## Clone Repo 89 | 90 | git clone https://github.com/ubc/docker-canvas.git docker-canvas 91 | 92 | ## Generate Canvas Docker Image (with issues encountered on stable branch as of 2021-05-18) 93 | 94 | Based on SHA `9ad21650ebbee144bd96a28aab53507a1bcefc6c`. Still working as of tag `release\2021-06-23.26`. 95 | 96 | The official Canvas docker image might not be up-to-date with the version available on github. If you need an updated image, you will have to build it yourself. Check out Canvas from Instructure's github (make sure you're on the branch you need, e.g.: stable). *You will also need to copy the `Dockerfile.prod.with.fixes` file into the Canvas-lms repo* and run: 97 | 98 | docker build -t instructure/canvas-lms:stable -f Dockerfile.prod.with.fixes . 99 | 100 | Note that Instructure recommends at around 10 GB of RAM to build this image. This will build and tag the image as a newer version in your docker cache. 101 | 102 | Notes: 103 | - There is currently no Dockerfile in Canvas that will generate an easily runnable image. `Dockerfile.prod.with.fixes` is a stopgap to getting a working version of Canvas running without dory/dinghy. 104 | - It is a combination of Canvas repo's `Dockerfile` and `ubuntu.development.Dockerfile` files. 105 | - `yarn install` needs the `--network-timeout 600000 --network-concurrency 1` options or it will fail.` 106 | 107 | ## If it is the first time running: 108 | 109 | Initialize data by first starting the database: 110 | 111 | docker compose up -d db 112 | 113 | Wait a few moments for the database to start then (command might fail if database hasn't finished first time startup): 114 | 115 | CANVAS_RAILS5_2=1 docker compose run --rm app bundle exec rake db:create db:initial_setup 116 | 117 | When prompted enter default account email, password, and display name. Also choose to share usage data or not. 118 | 119 | The branding assets must also be manually generated when canvas is in production mode: 120 | 121 | docker compose run --rm app bundle exec rake canvas:compile_assets 122 | docker compose run --rm app bundle exec rake brand_configs:generate_and_upload_all 123 | 124 | Edit `/ect/hosts` and add the line: 125 | 126 | 127.0.0.1 docker_canvas_app 127 | 128 | Finally startup all the services: 129 | 130 | docker compose up -d 131 | 132 | Canvas is accessible at 133 | 134 | http://docker_canvas_app:8900/ 135 | 136 | MailHog (catches all out going mail from canvas) is accessible at 137 | 138 | http://localhost:8902/ 139 | 140 | # Running 141 | 142 | ## Start Server 143 | 144 | docker compose up -d 145 | 146 | ## Check Logs 147 | 148 | # app 149 | docker logs -f docker-canvas_app_1 150 | 151 | # more detailed app logs 152 | docker exec -it docker-canvas_app_1 tail -f log/production.log 153 | 154 | # worker 155 | docker logs -f docker-canvas_worker_1 156 | 157 | # db 158 | docker logs -f docker-canvas_db_1 159 | 160 | # redis 161 | docker logs -f docker-canvas_redis_1 162 | 163 | # mail 164 | docker logs -f docker-canvas_mail_1 165 | 166 | ## Stop Server 167 | 168 | docker compose stop 169 | 170 | ## Stop Server and Clean Up 171 | 172 | docker compose down 173 | rm -rf .data 174 | 175 | ## Update the DB 176 | 177 | CANVAS_RAILS5_2=1 docker compose run --rm app bundle exec rake db:migrate 178 | 179 | ## Communicating between projects 180 | 181 | The `docker-compose.yml` is setup to allow other docker compose projects to connect via external networks. In `docker-compose.yml` you will see 182 | 183 | ``` 184 | version: '3.8' 185 | services: 186 | ... 187 | app: &app 188 | ... 189 | networks: 190 | default: 191 | aliases: 192 | - app 193 | docker_canvas_bridge: 194 | aliases: 195 | - docker_canvas_app 196 | ... 197 | networks: 198 | docker_canvas_bridge: 199 | name: docker_canvas_bridge 200 | ``` 201 | 202 | To include the network in another project you just need to add the network (in additional to the default network) and then you can use the alias `docker_canvas_app:8900` to connect to the canvas app. For example in another project do: 203 | 204 | ``` 205 | version: '3.8' 206 | services: 207 | ... 208 | app: 209 | ... 210 | networks: 211 | - default 212 | - docker_canvas_bridge 213 | ... 214 | networks: 215 | docker_canvas_bridge: 216 | external: true 217 | ``` 218 | 219 | Finally you need to edit your `/ect/hosts` and add the line (if you haven't already): 220 | 221 | 127.0.0.1 docker_canvas_app 222 | 223 | So your local machine can connect to Canvas using the same alias (this is important for LTI launch redirects). 224 | 225 | # Environment Variable Configuration 226 | 227 | ## Passenger 228 | 229 | `PASSENGER_STARTUP_TIMEOUT`: Increase to avoid first time startup Passenger timeout errors (can take a while and the timeout might be too short). 230 | 231 | # Update postgres version (from 9.6 to 12.4) 232 | 233 | 1. `docker compose down` 234 | 1. open `docker-compose.yml` in an editor 235 | - comment out the regular section of `db` 236 | - uncomment the `tianon/postgres-upgrade` section of `db` 237 | 1. in file explorer / Finder, open the `.data` folder 238 | - back a backup copy of the entire `postgres` folder 239 | - rename the `postgres` folder to `postgres-9.6` 240 | 1. `docker compose up -d` 241 | 1. wait a few minutes for the upgrade to happen 242 | 1. `docker compose down` 243 | 1. in file explorer / Finder, open the `.data` folder 244 | - rename the `postgres-12` folder to `postgres` 245 | - edit the `pg_hba.conf` file in `postgres` and add `host all all all md5` to the bottom 246 | 1. open `docker-compose.yml` in an editor 247 | - uncomment the regular section of `db` 248 | - comment out the `tianon/postgres-upgrade` section of `db` 249 | 1. `docker compose up -d` 250 | 251 | After you verify that the update worked, you can remove the backup copy of `postgres` and the `postgres-9.6` folders (or keep them as backups for later) 252 | -------------------------------------------------------------------------------- /deploy/cache_store.yml: -------------------------------------------------------------------------------- 1 | development: 2 | cache_store: redis_store 3 | 4 | production: 5 | cache_store: redis_store 6 | 7 | test: 8 | cache_store: redis_store -------------------------------------------------------------------------------- /deploy/cassandra.yml: -------------------------------------------------------------------------------- 1 | # production: 2 | # host: 10.11.12.13 3 | # port: 8500 4 | # ssl: true 5 | # environment: prod 6 | # dc: "consul" 7 | # acl_token: "xxxxxxxx-yyyy-zzzz-1111-222222222222" 8 | # test: 9 | # host: consul 10 | # port: 8500 11 | # ssl: false 12 | # environment: test 13 | # dc: "consul" 14 | # init_values: 15 | # canvas: 16 | # signing-secret: astringthatisactually32byteslong 17 | # encryption-secret: astringthatisactually32byteslong 18 | # rich-content-service: 19 | # app-host: rce.docker 20 | # cdn-host: rce.docker 21 | # address-book: 22 | # app-host: http://address-book.docker 23 | # secret: opensesame 24 | # live-events-subscription-service: 25 | # app-host: http://les.docker 26 | # sad-panda: null 27 | # development: 28 | # host: consul 29 | # port: 8500 30 | # ssl: false 31 | # environment: "dev" 32 | # init_values_witnout_env: 33 | # canvas: 34 | # signing-secret: astringthatisactually32byteslong 35 | # encryption-secret: astringthatisactually32byteslong 36 | # rich-content-service: 37 | # app-host: rce.docker 38 | # cdn-host: null 39 | # address-book: 40 | # app-host: http://address-book.docker 41 | # secret: opensesame 42 | # live-events-subscription-service: 43 | # app-host: http://les.docker 44 | # sad-panda: null 45 | # live-events: 46 | # aws_endpoint: http://kinesis.canvaslms.docker 47 | # kinesis_stream_name: live-events 48 | # init_values: 49 | # math-man: 50 | # base_url: 'http://mathman.docker' 51 | # use_for_svg: 'false' 52 | # use_for_mml: 'false' 53 | -------------------------------------------------------------------------------- /deploy/consul.yml: -------------------------------------------------------------------------------- 1 | # production: 2 | # host: 10.11.12.13 3 | # port: 8500 4 | # ssl: true 5 | # environment: prod 6 | # dc: "consul" 7 | # acl_token: "xxxxxxxx-yyyy-zzzz-1111-222222222222" 8 | # test: 9 | # host: consul 10 | # port: 8500 11 | # ssl: false 12 | # environment: test 13 | # dc: "consul" 14 | # init_values: 15 | # canvas: 16 | # signing-secret: astringthatisactually32byteslong 17 | # encryption-secret: astringthatisactually32byteslong 18 | # rich-content-service: 19 | # app-host: rce.docker 20 | # cdn-host: rce.docker 21 | # address-book: 22 | # app-host: http://address-book.docker 23 | # secret: opensesame 24 | # live-events-subscription-service: 25 | # app-host: http://les.docker 26 | # sad-panda: null 27 | # development: 28 | # host: consul 29 | # port: 8500 30 | # ssl: false 31 | # environment: "dev" 32 | # init_values_witnout_env: 33 | # canvas: 34 | # signing-secret: astringthatisactually32byteslong 35 | # encryption-secret: astringthatisactually32byteslong 36 | # rich-content-service: 37 | # app-host: rce.docker 38 | # cdn-host: null 39 | # address-book: 40 | # app-host: http://address-book.docker 41 | # secret: opensesame 42 | # live-events-subscription-service: 43 | # app-host: http://les.docker 44 | # sad-panda: null 45 | # live-events: 46 | # aws_endpoint: http://kinesis.canvaslms.docker 47 | # kinesis_stream_name: live-events 48 | # init_values: 49 | # math-man: 50 | # base_url: 'http://mathman.docker' 51 | # use_for_svg: 'false' 52 | # use_for_mml: 'false' 53 | -------------------------------------------------------------------------------- /deploy/database.yml: -------------------------------------------------------------------------------- 1 | # do not create a queue: section for your test environment 2 | 3 | default: &default 4 | adapter: postgresql 5 | encoding: utf8 6 | host: <%= ENV['DB_HOST'] %> 7 | port: <%= ENV['DB_PORT'] %> 8 | database: <%= ENV['DB_NAME'] %> 9 | username: <%= ENV['DB_USERNAME'] %> 10 | password: <%= ENV['DB_PASSWORD'] %> 11 | timeout: 5000 12 | 13 | development: 14 | <<: *default 15 | 16 | production: 17 | <<: *default 18 | 19 | test: 20 | <<: *default 21 | database: <%= ENV['DB_NAME'] %>_test 22 | -------------------------------------------------------------------------------- /deploy/delayed_jobs.yml: -------------------------------------------------------------------------------- 1 | default: 2 | workers: 3 | - queue: canvas_queue 4 | -------------------------------------------------------------------------------- /deploy/development-local.rb: -------------------------------------------------------------------------------- 1 | config.cache_classes = true 2 | config.action_controller.perform_caching = true 3 | config.action_view.cache_template_loading = true -------------------------------------------------------------------------------- /deploy/domain.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | domain: <%= ENV['DOMAIN'] %> 3 | 4 | development: 5 | <<: *default 6 | 7 | production: 8 | <<: *default 9 | 10 | test: 11 | <<: *default -------------------------------------------------------------------------------- /deploy/dynamic_settings.yml: -------------------------------------------------------------------------------- 1 | # this config file is useful if you don't want to run a consul 2 | # cluster with canvas. Just provide the config data you would 3 | # like for the DynamicSettings class to find, and it will use 4 | # it whenever a call for consul data is issued. Data should be 5 | # shaped like the example below, one key for the related set of data, 6 | # and a hash of key/value pairs (no nesting) 7 | default: &default 8 | store: 9 | canvas: 10 | lti-keys: 11 | # these are all the same JWK but with different kid 12 | # to generate a new key, run the following in a Canvas console: 13 | # 14 | # key = OpenSSL::PKey::RSA.generate(2048) 15 | # key.public_key.to_jwk(kid: Time.now.utc.iso8601).to_json 16 | jwk-past.json: '{"kty":"RSA","e":"AQAB","n":"uX1MpfEMQCBUMcj0sBYI-iFaG5Nodp3C6OlN8uY60fa5zSBd83-iIL3n_qzZ8VCluuTLfB7rrV_tiX727XIEqQ","kid":"2018-05-18T22:33:20Z","d":"pYwR64x-LYFtA13iHIIeEvfPTws50ZutyGfpHN-kIZz3k-xVpun2Hgu0hVKZMxcZJ9DkG8UZPqD-zTDbCmCyLQ","p":"6OQ2bi_oY5fE9KfQOcxkmNhxDnIKObKb6TVYqOOz2JM","q":"y-UBef95njOrqMAxJH1QPds3ltYWr8QgGgccmcATH1M","dp":"Ol_xkL7rZgNFt_lURRiJYpJmDDPjgkDVuafIeFTS4Ic","dq":"RtzDY5wXr5TzrwWEztLCpYzfyAuF_PZj1cfs976apsM","qi":"XA5wnwIrwe5MwXpaBijZsGhKJoypZProt47aVCtWtPE"}' 17 | jwk-present.json: '{"kty":"RSA","e":"AQAB","n":"uX1MpfEMQCBUMcj0sBYI-iFaG5Nodp3C6OlN8uY60fa5zSBd83-iIL3n_qzZ8VCluuTLfB7rrV_tiX727XIEqQ","kid":"2018-06-18T22:33:20Z","d":"pYwR64x-LYFtA13iHIIeEvfPTws50ZutyGfpHN-kIZz3k-xVpun2Hgu0hVKZMxcZJ9DkG8UZPqD-zTDbCmCyLQ","p":"6OQ2bi_oY5fE9KfQOcxkmNhxDnIKObKb6TVYqOOz2JM","q":"y-UBef95njOrqMAxJH1QPds3ltYWr8QgGgccmcATH1M","dp":"Ol_xkL7rZgNFt_lURRiJYpJmDDPjgkDVuafIeFTS4Ic","dq":"RtzDY5wXr5TzrwWEztLCpYzfyAuF_PZj1cfs976apsM","qi":"XA5wnwIrwe5MwXpaBijZsGhKJoypZProt47aVCtWtPE"}' 18 | jwk-future.json: '{"kty":"RSA","e":"AQAB","n":"uX1MpfEMQCBUMcj0sBYI-iFaG5Nodp3C6OlN8uY60fa5zSBd83-iIL3n_qzZ8VCluuTLfB7rrV_tiX727XIEqQ","kid":"2018-07-18T22:33:20Z","d":"pYwR64x-LYFtA13iHIIeEvfPTws50ZutyGfpHN-kIZz3k-xVpun2Hgu0hVKZMxcZJ9DkG8UZPqD-zTDbCmCyLQ","p":"6OQ2bi_oY5fE9KfQOcxkmNhxDnIKObKb6TVYqOOz2JM","q":"y-UBef95njOrqMAxJH1QPds3ltYWr8QgGgccmcATH1M","dp":"Ol_xkL7rZgNFt_lURRiJYpJmDDPjgkDVuafIeFTS4Ic","dq":"RtzDY5wXr5TzrwWEztLCpYzfyAuF_PZj1cfs976apsM","qi":"XA5wnwIrwe5MwXpaBijZsGhKJoypZProt47aVCtWtPE"}' 19 | 20 | development: 21 | <<: *default 22 | 23 | production: 24 | <<: *default 25 | 26 | test: 27 | <<: *default -------------------------------------------------------------------------------- /deploy/outgoing_mail.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | address: <%= ENV['MAIL_SERVER'] %> 3 | port: <%= ENV['MAIL_PORT'] %> 4 | user_name: <%= ENV['MAIL_USERNAME'] %> 5 | password: <%= ENV['MAIL_PASSWORD'] %> 6 | authentication: "plain" # plain, login, or cram_md5 7 | domain: <%= ENV['MAIL_DOMAIN'] %> 8 | outgoing_address: <%= ENV['MAIL_DEFAULT_SENDER_ADDRESS'] %> 9 | default_name: <%= ENV['MAIL_DEFAULT_SENDER_NAME'] %> 10 | 11 | development: 12 | <<: *default 13 | 14 | production: 15 | <<: *default 16 | 17 | test: 18 | <<: *default -------------------------------------------------------------------------------- /deploy/redis.yml: -------------------------------------------------------------------------------- 1 | 2 | default: &default 3 | servers: 4 | - <%= ENV['REDIS_SERVER'] %> 5 | 6 | test: 7 | <<: *default 8 | 9 | development: 10 | <<: *default 11 | 12 | production: 13 | <<: *default -------------------------------------------------------------------------------- /deploy/security.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | encryption_key: facdd3a131ddd8988b14f6e4e01039c93cfa0160 3 | lti_iss: https://docker-canvas.instructure.com 4 | lti_grant_host: <%= ENV['DOMAIN'] %> 5 | 6 | production: 7 | <<: *default 8 | 9 | development: 10 | <<: *default 11 | 12 | test: 13 | <<: *default -------------------------------------------------------------------------------- /deploy/selenium.yml: -------------------------------------------------------------------------------- 1 | # test: 2 | # remote_url_firefox: http://selenium-firefox:4444/wd/hub 3 | # remote_url_chrome: http://selenium-chrome:4444/wd/hub 4 | # browser: chrome 5 | # # auto_open_devtools: true -------------------------------------------------------------------------------- /deploy/vault.yml: -------------------------------------------------------------------------------- 1 | # production: 2 | # addr: https://vault.blah.com 3 | # token_path: /path/to/token/on/disk 4 | # kv_mount: app-canvas 5 | # test: 6 | # addr: http://vault:8200 7 | # token: canvas_root_token 8 | # kv_mount: app-canvas 9 | # development: 10 | # addr: http://vault:8200 11 | # token: canvas_root_token 12 | # kv_mount: app-canvas -------------------------------------------------------------------------------- /docker-compose.override.skip-dory-example.yml: -------------------------------------------------------------------------------- 1 | # See doc/docker/README.md or https://github.com/instructure/canvas-lms/tree/master/doc/docker 2 | version: '2.3' 3 | services: 4 | jobs: &BASE 5 | build: 6 | context: . 7 | volumes: 8 | - .:/usr/src/app 9 | - api_docs:/usr/src/app/public/doc/api 10 | - brandable_css_brands:/usr/src/app/app/stylesheets/brandable_css_brands 11 | - bundler:/home/docker/.bundle/ 12 | - canvas-docker-gems:/home/docker/.gem/ 13 | - canvas-media_es:/usr/src/app/packages/canvas-media/es 14 | - canvas-media_lib:/usr/src/app/packages/canvas-media/lib 15 | - canvas-media_node_modules:/usr/src/app/packages/canvas-media/node_modules 16 | - canvas-planner_node_modules:/usr/src/app/packages/canvas-planner/node_modules 17 | - canvas-planner_lib:/usr/src/app/packages/canvas-planner/lib 18 | - canvas-rce_canvas:/usr/src/app/packages/canvas-rce/canvas 19 | - canvas-rce_lib:/usr/src/app/packages/canvas-rce/lib 20 | - canvas-rce_node_modules:/usr/src/app/packages/canvas-rce/node_modules 21 | - jest-moxios-utils_node_modules:/usr/src/app/packages/jest-moxios-utils/node_modules 22 | - js-utils_es:/usr/src/app/packages/js-utils/es 23 | - js-utils_lib:/usr/src/app/packages/js-utils/lib 24 | - js-utils_node_modules:/usr/src/app/packages/js-utils/node_modules 25 | - k5uploader_es:/usr/src/app/packages/k5uploader/es 26 | - k5uploader_lib:/usr/src/app/packages/k5uploader/lib 27 | - k5uploader_node_modules:/usr/src/app/packages/k5uploader/node_modules 28 | - locales:/usr/src/app/config/locales/generated 29 | - log:/usr/src/app/log 30 | - node_modules:/usr/src/app/node_modules 31 | - old-copy-of-react-14-that-is-just-here-so-if-analytics-is-checked-out-it-doesnt-change-yarn.lock_node_modules:/usr/src/app/packages/old-copy-of-react-14-that-is-just-here-so-if-analytics-is-checked-out-it-doesnt-change-yarn.lock/node_modules 32 | - pacts:/usr/src/app/pacts 33 | - public_dist:/usr/src/app/public/dist 34 | - reports:/usr/src/app/reports 35 | - styleguide:/usr/src/app/app/views/info 36 | - tmp:/usr/src/app/tmp 37 | - translations:/usr/src/app/public/javascripts/translations 38 | - yardoc:/usr/src/app/.yardoc 39 | - yarn-cache:/home/docker/.cache/yarn 40 | environment: &BASE-ENV 41 | ENCRYPTION_KEY: facdd3a131ddd8988b14f6e4e01039c93cfa0160 42 | RAILS_ENV: development 43 | 44 | webpack: 45 | <<: *BASE 46 | command: yarn run webpack 47 | env_file: 48 | - ./.env 49 | 50 | web: 51 | <<: *BASE 52 | environment: 53 | <<: *BASE-ENV 54 | VIRTUAL_HOST: .canvas.docker 55 | HTTPS_METHOD: noredirect 56 | # AR_QUERY_TRACE: 'true' 57 | # AR_QUERY_TRACE_TYPE: 'all' # 'all', 'write', or 'read' 58 | # AR_QUERY_TRACE_LINES: 10 59 | ports: 60 | - "9100:80" 61 | 62 | postgres: 63 | volumes: 64 | - pg_data:/var/lib/postgresql/data 65 | 66 | githook_installer: 67 | build: 68 | context: . 69 | dockerfile: 'Dockerfile.githook' 70 | volumes: 71 | - ./.git:/tmp/.git 72 | - ./hooks:/tmp/hooks 73 | - ./script:/tmp/script 74 | 75 | socat: 76 | image: alpine/socat:1.7.4.3-r1 77 | depends_on: 78 | - web 79 | command: "TCP-LISTEN:9100,fork,reuseaddr TCP:web:80" 80 | networks: 81 | default: 82 | aliases: 83 | - socat 84 | docker_canvas_bridge: 85 | aliases: 86 | - canvas.docker 87 | 88 | networks: 89 | docker_canvas_bridge: 90 | name: docker_canvas_bridge 91 | 92 | volumes: 93 | api_docs: {} 94 | brandable_css_brands: {} 95 | bundler: {} 96 | canvas-docker-gems: {} 97 | canvas-media_es: {} 98 | canvas-media_lib: {} 99 | canvas-media_node_modules: {} 100 | canvas-planner_node_modules: {} 101 | canvas-planner_lib: {} 102 | canvas-rce_canvas: {} 103 | canvas-rce_lib: {} 104 | canvas-rce_node_modules: {} 105 | i18nliner_node_modules: {} 106 | jest-moxios-utils_node_modules: {} 107 | js-utils_es: {} 108 | js-utils_lib: {} 109 | js-utils_node_modules: {} 110 | k5uploader_es: {} 111 | k5uploader_lib: {} 112 | k5uploader_node_modules: {} 113 | locales: {} 114 | log: {} 115 | node_modules: {} 116 | old-copy-of-react-14-that-is-just-here-so-if-analytics-is-checked-out-it-doesnt-change-yarn.lock_node_modules: {} 117 | pg_data: {} 118 | pacts: {} 119 | public_dist: {} 120 | reports: {} 121 | styleguide: {} 122 | tmp: {} 123 | translations: {} 124 | yardoc: {} 125 | yarn-cache: {} 126 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | db: 4 | image: postgres:12.4 5 | environment: 6 | - POSTGRES_USER=canvas 7 | - POSTGRES_PASSWORD=canvas 8 | - POSTGRES_DB=canvas 9 | volumes: 10 | - ./.data/postgres:/var/lib/postgresql/data 11 | ports: 12 | - "15432:5432" 13 | # # use https://github.com/tianon/docker-postgres-upgrade for upgrading postgres as needed 14 | # # also need to add `host all all all md5` to `.data/postgres/pg_hba.conf` after copying over the updated files 15 | # image: tianon/postgres-upgrade:9.6-to-12 16 | # environment: 17 | # - POSTGRES_USER=canvas 18 | # - POSTGRES_PASSWORD=canvas 19 | # - POSTGRES_DB=canvas 20 | # volumes: 21 | # - ./.data/postgres:/var/lib/postgresql/data 22 | # # copy data from .data/postgres into here before running image 23 | # - ./.data/postgres-9.6:/var/lib/postgresql/9.6/data 24 | # # copy data from here into .data/postgres after running image 25 | # - ./.data/postgres-12:/var/lib/postgresql/12/data 26 | # ports: 27 | # - "15432:5432" 28 | redis: 29 | image: redis:6.0-alpine 30 | command: redis-server --appendonly yes 31 | volumes: 32 | - ./.data/redis:/data 33 | mail: 34 | image: mailhog/mailhog:v1.0.1 35 | ports: 36 | - "8902:8025" 37 | app: &app 38 | image: instructure/canvas-lms:stable 39 | command: bash -c "./wait-for-it.sh -t 40 db:5432 && /usr/src/entrypoint" 40 | environment: 41 | - DB_DRIVER=postgresql 42 | - DB_HOST=db 43 | - DB_PORT=5432 44 | - DB_USERNAME=canvas 45 | - DB_PASSWORD=canvas 46 | - DB_NAME=canvas 47 | - RAILS_ENV=production 48 | - REDIS_SERVER=redis://redis:6379 49 | - MAIL_SERVER=mail 50 | - MAIL_PORT=1025 51 | - MAIL_USERNAME=canvas 52 | - MAIL_PASSWORD=canvas 53 | - MAIL_DOMAIN=example.com 54 | - MAIL_DEFAULT_SENDER_ADDRESS=canvas@example.com 55 | - MAIL_DEFAULT_SENDER_NAME=Canvas Admin 56 | - PASSENGER_STARTUP_TIMEOUT=300 57 | - DOMAIN=docker_canvas_app:8900 58 | - APP_DOMAIN=docker_canvas_app 59 | # need both ports to be equal or it'll redirect HTTP to HTTPS, and we 60 | # haven't setup HTTPS 61 | - CG_HTTP_PORT=8900 62 | - CG_HTTPS_PORT=8900 63 | volumes: 64 | # config 65 | - ./deploy/cache_store.yml:/usr/src/app/config/cache_store.yml:ro 66 | - ./deploy/cassandra.yml:/usr/src/app/config/cassandra.yml:ro 67 | - ./deploy/consul.yml:/usr/src/app/config/consul.yml:ro 68 | - ./deploy/database.yml:/usr/src/app/config/database.yml:ro 69 | - ./deploy/delayed_jobs.yml:/usr/src/app/config/delayed_jobs.yml:ro 70 | - ./deploy/domain.yml:/usr/src/app/config/domain.yml:ro 71 | - ./deploy/dynamic_settings.yml:/usr/src/app/config/dynamic_settings.yml:ro 72 | - ./deploy/outgoing_mail.yml:/usr/src/app/config/outgoing_mail.yml:ro 73 | - ./deploy/redis.yml:/usr/src/app/config/redis.yml:ro 74 | - ./deploy/security.yml:/usr/src/app/config/security.yml:ro 75 | - ./deploy/selenium.yml:/usr/src/app/config/selenium.yml:ro 76 | - ./deploy/vault.yml:/usr/src/app/config/vault.yml:ro 77 | # override config settings 78 | - ./deploy/development-local.rb:/usr/src/app/config/environments/development-local.rb:ro 79 | # persistence 80 | - ./.data/public/dist/brandable_css:/usr/src/app/public/dist/brandable_css 81 | - ./.data/tmp:/usr/src/app/tmp 82 | # wait for it script 83 | - ./wait-for-it.sh:/usr/src/app/wait-for-it.sh:ro 84 | # fixes/overrides 85 | - ./override/fill_custom_claim_columns_for_resource_link.rb:/usr/src/app/lib/data_fixup/lti/fill_custom_claim_columns_for_resource_link.rb 86 | - ./override/fill_lookup_uuid_and_resource_link_uuid_columns.rb:/usr/src/app/lib/data_fixup/lti/fill_lookup_uuid_and_resource_link_uuid_columns.rb 87 | - ./override/20210201170030_fill_lookup_uuid_and_resource_link_uuid_columns_at_lti_resource_links.rb:/usr/src/app/db/migrate/20210201170030_fill_lookup_uuid_and_resource_link_uuid_columns_at_lti_resource_links.rb 88 | ports: 89 | - "8900:8900" 90 | depends_on: 91 | - db 92 | - redis 93 | - mail 94 | networks: 95 | default: 96 | aliases: 97 | - app 98 | docker_canvas_bridge: 99 | aliases: 100 | - docker_canvas_app 101 | worker: 102 | <<: *app 103 | command: bundle exec script/delayed_job run 104 | ports: [] 105 | networks: 106 | - default 107 | 108 | networks: 109 | docker_canvas_bridge: 110 | name: docker_canvas_bridge 111 | -------------------------------------------------------------------------------- /override/20210201170030_fill_lookup_uuid_and_resource_link_uuid_columns_at_lti_resource_links.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Copyright (C) 2021 - present Instructure, Inc. 4 | # 5 | # This file is part of Canvas. 6 | # 7 | # Canvas is free software: you can redistribute it and/or modify it under 8 | # the terms of the GNU Affero General Public License as published by the Free 9 | # Software Foundation, version 3 of the License. 10 | # 11 | # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY 12 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 14 | # details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License along 17 | # with this program. If not, see . 18 | 19 | class FillLookupUuidAndResourceLinkUuidColumnsAtLtiResourceLinks < ActiveRecord::Migration[5.2] 20 | tag :postdeploy 21 | 22 | def up 23 | DataFixup::Lti::FillLookupUuidAndResourceLinkUuidColumns.run 24 | end 25 | 26 | def down; end 27 | end 28 | -------------------------------------------------------------------------------- /override/fill_custom_claim_columns_for_resource_link.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright (C) 2020 - present Instructure, Inc. 5 | # 6 | # This file is part of Canvas. 7 | # 8 | # Canvas is free software: you can redistribute it and/or modify it under 9 | # the terms of the GNU Affero General Public License as published by the Free 10 | # Software Foundation, version 3 of the License. 11 | # 12 | # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 15 | # details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License along 18 | # with this program. If not, see . 19 | 20 | module DataFixup::Lti::FillCustomClaimColumnsForResourceLink 21 | def self.run 22 | update_context! 23 | drop_resource_links_without_a_context 24 | 25 | Lti::ResourceLink.in_batches do |resource_links| 26 | update_lookup_id!(resource_links) 27 | end 28 | end 29 | 30 | def self.drop_resource_links_without_a_context 31 | Lti::LineItem.connection.execute(%{ 32 | DELETE FROM #{Lti::LineItem.quoted_table_name} 33 | WHERE lti_resource_link_id IN ( 34 | SELECT ID FROM #{Lti::ResourceLink.quoted_table_name} 35 | WHERE context_type IS NULL OR context_id IS NULL 36 | ); 37 | }) 38 | 39 | Lti::ResourceLink.connection.execute(%{ 40 | DELETE FROM #{Lti::ResourceLink.quoted_table_name} 41 | WHERE context_type IS NULL OR context_id IS NULL; 42 | }) 43 | end 44 | 45 | def self.update_context! 46 | Lti::ResourceLink. 47 | joins("INNER JOIN #{Assignment.quoted_table_name} ON assignments.lti_context_id = lti_resource_links.resource_link_id"). 48 | update_all("context_type = 'Assignment', context_id = assignments.id") 49 | end 50 | 51 | def self.update_lookup_id!(resource_links) 52 | resource_links.each do |resource_link| 53 | Lti::ResourceLink.connection.execute(%{ 54 | UPDATE #{Lti::ResourceLink.quoted_table_name} 55 | SET lookup_id = '#{SecureRandom.uuid}' 56 | WHERE id = #{resource_link.id}; 57 | }) 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /override/fill_lookup_uuid_and_resource_link_uuid_columns.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Copyright (C) 2021 - present Instructure, Inc. 5 | # 6 | # This file is part of Canvas. 7 | # 8 | # Canvas is free software: you can redistribute it and/or modify it under 9 | # the terms of the GNU Affero General Public License as published by the Free 10 | # Software Foundation, version 3 of the License. 11 | # 12 | # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 15 | # details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License along 18 | # with this program. If not, see . 19 | 20 | module DataFixup::Lti::FillLookupUuidAndResourceLinkUuidColumns 21 | def self.run 22 | resource_links_to_update.in_batches do |resource_links| 23 | update_columns!(resource_links) 24 | end 25 | end 26 | 27 | # Our assumption is if the data-type is not valid anymore, it was due to an 28 | # accidental action (e.g. some change using rails console). 29 | # Canvas is responsible for generating the UUID and uses rails before actions 30 | # as Lti::ResourceLink.generate_lookup_id/generate_resource_link_id to set 31 | # the UUID properly. 32 | # 33 | # We figure out that we could: 34 | # 1. drop the record with invalid UUID, or; 35 | # 2. set a new UUID value to the columns that have invalid value; 36 | # 37 | # So, we decide that will be better to follow the second approach by 38 | # re-generate the UUID and set it to the old and the new column for consistency. 39 | def self.update_columns!(resource_links) 40 | resource_links.each do |resource_link| 41 | Lti::ResourceLink.connection.execute(%{ 42 | UPDATE #{Lti::ResourceLink.quoted_table_name} 43 | SET lookup_uuid = '#{resource_link.lookup_id}', 44 | resource_link_uuid = '#{resource_link.resource_link_id}' 45 | WHERE id = #{resource_link.id}; 46 | }) 47 | end 48 | end 49 | 50 | def self.resource_links_to_update 51 | Lti::ResourceLink.where(lookup_uuid: nil).or(Lti::ResourceLink.where(resource_link_uuid: nil)) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to test if a given TCP host/port are available 3 | 4 | WAITFORIT_cmdname=${0##*/} 5 | 6 | echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } 7 | 8 | usage() 9 | { 10 | cat << USAGE >&2 11 | Usage: 12 | $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] 13 | -h HOST | --host=HOST Host or IP under test 14 | -p PORT | --port=PORT TCP port under test 15 | Alternatively, you specify the host and port as host:port 16 | -s | --strict Only execute subcommand if the test succeeds 17 | -q | --quiet Don't output any status messages 18 | -t TIMEOUT | --timeout=TIMEOUT 19 | Timeout in seconds, zero for no timeout 20 | -- COMMAND ARGS Execute command with args after the test finishes 21 | USAGE 22 | exit 1 23 | } 24 | 25 | wait_for() 26 | { 27 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then 28 | echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" 29 | else 30 | echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" 31 | fi 32 | WAITFORIT_start_ts=$(date +%s) 33 | while : 34 | do 35 | if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then 36 | nc -z $WAITFORIT_HOST $WAITFORIT_PORT 37 | WAITFORIT_result=$? 38 | else 39 | (echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 40 | WAITFORIT_result=$? 41 | fi 42 | if [[ $WAITFORIT_result -eq 0 ]]; then 43 | WAITFORIT_end_ts=$(date +%s) 44 | echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" 45 | break 46 | fi 47 | sleep 1 48 | done 49 | return $WAITFORIT_result 50 | } 51 | 52 | wait_for_wrapper() 53 | { 54 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 55 | if [[ $WAITFORIT_QUIET -eq 1 ]]; then 56 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & 57 | else 58 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & 59 | fi 60 | WAITFORIT_PID=$! 61 | trap "kill -INT -$WAITFORIT_PID" INT 62 | wait $WAITFORIT_PID 63 | WAITFORIT_RESULT=$? 64 | if [[ $WAITFORIT_RESULT -ne 0 ]]; then 65 | echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" 66 | fi 67 | return $WAITFORIT_RESULT 68 | } 69 | 70 | # process arguments 71 | while [[ $# -gt 0 ]] 72 | do 73 | case "$1" in 74 | *:* ) 75 | WAITFORIT_hostport=(${1//:/ }) 76 | WAITFORIT_HOST=${WAITFORIT_hostport[0]} 77 | WAITFORIT_PORT=${WAITFORIT_hostport[1]} 78 | shift 1 79 | ;; 80 | --child) 81 | WAITFORIT_CHILD=1 82 | shift 1 83 | ;; 84 | -q | --quiet) 85 | WAITFORIT_QUIET=1 86 | shift 1 87 | ;; 88 | -s | --strict) 89 | WAITFORIT_STRICT=1 90 | shift 1 91 | ;; 92 | -h) 93 | WAITFORIT_HOST="$2" 94 | if [[ $WAITFORIT_HOST == "" ]]; then break; fi 95 | shift 2 96 | ;; 97 | --host=*) 98 | WAITFORIT_HOST="${1#*=}" 99 | shift 1 100 | ;; 101 | -p) 102 | WAITFORIT_PORT="$2" 103 | if [[ $WAITFORIT_PORT == "" ]]; then break; fi 104 | shift 2 105 | ;; 106 | --port=*) 107 | WAITFORIT_PORT="${1#*=}" 108 | shift 1 109 | ;; 110 | -t) 111 | WAITFORIT_TIMEOUT="$2" 112 | if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi 113 | shift 2 114 | ;; 115 | --timeout=*) 116 | WAITFORIT_TIMEOUT="${1#*=}" 117 | shift 1 118 | ;; 119 | --) 120 | shift 121 | WAITFORIT_CLI=("$@") 122 | break 123 | ;; 124 | --help) 125 | usage 126 | ;; 127 | *) 128 | echoerr "Unknown argument: $1" 129 | usage 130 | ;; 131 | esac 132 | done 133 | 134 | if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then 135 | echoerr "Error: you need to provide a host and port to test." 136 | usage 137 | fi 138 | 139 | WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} 140 | WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} 141 | WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} 142 | WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} 143 | 144 | # check to see if timeout is from busybox? 145 | WAITFORIT_TIMEOUT_PATH=$(type -p timeout) 146 | WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) 147 | if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then 148 | WAITFORIT_ISBUSY=1 149 | WAITFORIT_BUSYTIMEFLAG="-t" 150 | 151 | else 152 | WAITFORIT_ISBUSY=0 153 | WAITFORIT_BUSYTIMEFLAG="" 154 | fi 155 | 156 | if [[ $WAITFORIT_CHILD -gt 0 ]]; then 157 | wait_for 158 | WAITFORIT_RESULT=$? 159 | exit $WAITFORIT_RESULT 160 | else 161 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then 162 | wait_for_wrapper 163 | WAITFORIT_RESULT=$? 164 | else 165 | wait_for 166 | WAITFORIT_RESULT=$? 167 | fi 168 | fi 169 | 170 | if [[ $WAITFORIT_CLI != "" ]]; then 171 | if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then 172 | echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" 173 | exit $WAITFORIT_RESULT 174 | fi 175 | exec "${WAITFORIT_CLI[@]}" 176 | else 177 | exit $WAITFORIT_RESULT 178 | fi 179 | --------------------------------------------------------------------------------