Showing preview only (2,642K chars total). Download the full file or copy to clipboard to get everything.
Repository: cookpad/barbeque
Branch: master
Commit: 0bd975c84da6
Files: 253
Total size: 2.5 MB
Directory structure:
gitextract_3xnxz7z0/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .rspec
├── CHANGELOG.md
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app/
│ ├── assets/
│ │ ├── config/
│ │ │ └── barbeque_manifest.js
│ │ ├── images/
│ │ │ └── barbeque/
│ │ │ └── .keep
│ │ ├── javascripts/
│ │ │ └── barbeque/
│ │ │ ├── application.js
│ │ │ ├── job_definitions.js
│ │ │ └── job_queues.js
│ │ └── stylesheets/
│ │ └── barbeque/
│ │ ├── application.css
│ │ └── job_definitions.css
│ ├── controllers/
│ │ └── barbeque/
│ │ ├── api/
│ │ │ ├── application_controller.rb
│ │ │ ├── job_executions_controller.rb
│ │ │ ├── job_retries_controller.rb
│ │ │ └── revision_locks_controller.rb
│ │ ├── application_controller.rb
│ │ ├── apps_controller.rb
│ │ ├── job_definitions_controller.rb
│ │ ├── job_executions_controller.rb
│ │ ├── job_queues_controller.rb
│ │ ├── job_retries_controller.rb
│ │ ├── monitors_controller.rb
│ │ └── sns_subscriptions_controller.rb
│ ├── helpers/
│ │ └── barbeque/
│ │ ├── application_helper.rb
│ │ ├── job_definitions_helper.rb
│ │ └── job_executions_helper.rb
│ ├── models/
│ │ ├── barbeque/
│ │ │ ├── api/
│ │ │ │ ├── application_resource.rb
│ │ │ │ ├── database_maintenance_resource.rb
│ │ │ │ ├── job_execution_resource.rb
│ │ │ │ ├── job_retry_resource.rb
│ │ │ │ └── revision_lock_resource.rb
│ │ │ ├── app.rb
│ │ │ ├── application_record.rb
│ │ │ ├── docker_container.rb
│ │ │ ├── ecs_hako_task.rb
│ │ │ ├── job_definition.rb
│ │ │ ├── job_execution.rb
│ │ │ ├── job_queue.rb
│ │ │ ├── job_retry.rb
│ │ │ ├── retry_config.rb
│ │ │ ├── slack_notification.rb
│ │ │ └── sns_subscription.rb
│ │ └── concerns/
│ │ └── .keep
│ ├── services/
│ │ └── barbeque/
│ │ ├── message_enqueuing_service.rb
│ │ ├── message_retrying_service.rb
│ │ └── sns_subscription_service.rb
│ └── views/
│ ├── barbeque/
│ │ ├── apps/
│ │ │ ├── _form.html.haml
│ │ │ ├── edit.html.haml
│ │ │ ├── index.html.haml
│ │ │ ├── new.html.haml
│ │ │ └── show.html.haml
│ │ ├── job_definitions/
│ │ │ ├── _form.html.haml
│ │ │ ├── _retry_configuration_field.html.haml
│ │ │ ├── _slack_notification_field.html.haml
│ │ │ ├── edit.html.haml
│ │ │ ├── index.html.haml
│ │ │ ├── new.html.haml
│ │ │ ├── show.html.haml
│ │ │ └── stats.html.haml
│ │ ├── job_executions/
│ │ │ └── show.html.haml
│ │ ├── job_queues/
│ │ │ ├── _form.html.haml
│ │ │ ├── edit.html.haml
│ │ │ ├── index.html.haml
│ │ │ ├── new.html.haml
│ │ │ └── show.html.haml
│ │ ├── job_retries/
│ │ │ └── show.html.haml
│ │ ├── monitors/
│ │ │ └── index.html.haml
│ │ └── sns_subscriptions/
│ │ ├── _form.html.haml
│ │ ├── edit.html.haml
│ │ ├── index.html.haml
│ │ ├── new.html.haml
│ │ └── show.html.haml
│ └── layouts/
│ └── barbeque/
│ ├── _header.html.haml
│ ├── _sidebar.html.haml
│ ├── application.html.haml
│ ├── apps.html.haml
│ ├── job_definitions.html.haml
│ ├── job_executions.html.haml
│ ├── job_queues.html.haml
│ ├── job_retries.html.haml
│ ├── monitors.html.haml
│ └── sns_subscriptions.html.haml
├── barbeque.gemspec
├── bin/
│ └── rails
├── compose.yaml
├── config/
│ ├── initializers/
│ │ └── garage.rb
│ └── routes.rb
├── db/
│ └── migrate/
│ ├── 20160217020910_create_job_queues.rb
│ ├── 20160219010912_create_job_executions.rb
│ ├── 20160223060807_create_apps.rb
│ ├── 20160223183348_create_job_definitions.rb
│ ├── 20160225020801_add_job_definition_id_to_job_executions.rb
│ ├── 20160412083604_add_finished_at_to_job_executions.rb
│ ├── 20160415043427_create_slack_notifications.rb
│ ├── 20160509041452_add_job_queue_id_to_job_executions.rb
│ ├── 20160516041710_create_job_retries.rb
│ ├── 20160829023237_prefix_barbeque_to_tables.rb
│ ├── 20170420030157_create_barbeque_sns_subscriptions.rb
│ ├── 20170711085157_create_barbeque_docker_containers.rb
│ ├── 20170712075449_create_barbeque_ecs_hako_tasks.rb
│ ├── 20170724025542_add_index_to_job_execution_status.rb
│ ├── 20180411070937_add_index_to_barbeque_job_executions_created_at.rb
│ ├── 20190221050714_create_barbeque_retry_configs.rb
│ ├── 20190311034445_add_notify_failure_only_if_retry_limit_reached_to_barbeque_slack_notifications.rb
│ ├── 20190315052951_change_barbeque_retry_configs_base_delay_default_value.rb
│ ├── 20191029105530_change_job_name_to_case_sensitive.rb
│ └── 20240415080757_fix_collations.rb
├── doc/
│ ├── api/
│ │ ├── job_executions.md
│ │ ├── job_retries.md
│ │ └── revision_locks.md
│ └── toc.md
├── lib/
│ ├── barbeque/
│ │ ├── config.rb
│ │ ├── docker_image.rb
│ │ ├── engine.rb
│ │ ├── exception_handler.rb
│ │ ├── execution_log.rb
│ │ ├── execution_poller.rb
│ │ ├── executor/
│ │ │ ├── docker.rb
│ │ │ └── hako.rb
│ │ ├── executor.rb
│ │ ├── hako_s3_client.rb
│ │ ├── maintenance.rb
│ │ ├── message/
│ │ │ ├── base.rb
│ │ │ ├── invalid_message.rb
│ │ │ ├── job_execution.rb
│ │ │ ├── job_retry.rb
│ │ │ └── notification.rb
│ │ ├── message.rb
│ │ ├── message_handler/
│ │ │ ├── job_execution.rb
│ │ │ ├── job_retry.rb
│ │ │ └── notification.rb
│ │ ├── message_handler.rb
│ │ ├── message_queue.rb
│ │ ├── retry_poller.rb
│ │ ├── runner.rb
│ │ ├── slack_client.rb
│ │ ├── slack_notifier.rb
│ │ ├── version.rb
│ │ └── worker.rb
│ ├── barbeque.rb
│ └── tasks/
│ └── barbeque_tasks.rake
├── spec/
│ ├── barbeque/
│ │ ├── config_builder_spec.rb
│ │ ├── config_spec.rb
│ │ ├── docker_image_spec.rb
│ │ ├── exception_handler_spec.rb
│ │ ├── execution_log_spec.rb
│ │ ├── execution_poller_spec.rb
│ │ ├── executor/
│ │ │ ├── docker_spec.rb
│ │ │ └── hako_spec.rb
│ │ ├── executor_spec.rb
│ │ ├── message_handler/
│ │ │ ├── job_execution_spec.rb
│ │ │ ├── job_retry_spec.rb
│ │ │ └── notification_spec.rb
│ │ ├── message_queue_spec.rb
│ │ ├── message_spec.rb
│ │ ├── retry_poller_spec.rb
│ │ ├── runner_spec.rb
│ │ └── worker_spec.rb
│ ├── controllers/
│ │ └── barbeque/
│ │ ├── apps_controller_spec.rb
│ │ ├── job_definitions_controller_spec.rb
│ │ ├── job_executions_controller_spec.rb
│ │ ├── job_queues_controller_spec.rb
│ │ ├── job_retries_controller_spec.rb
│ │ └── sns_subscriptions_controller_spec.rb
│ ├── dummy/
│ │ ├── .gitignore
│ │ ├── Rakefile
│ │ ├── app/
│ │ │ ├── assets/
│ │ │ │ ├── config/
│ │ │ │ │ └── manifest.js
│ │ │ │ ├── images/
│ │ │ │ │ └── .keep
│ │ │ │ ├── javascripts/
│ │ │ │ │ ├── application.js
│ │ │ │ │ └── channels/
│ │ │ │ │ └── .keep
│ │ │ │ └── stylesheets/
│ │ │ │ └── application.css
│ │ │ ├── controllers/
│ │ │ │ ├── application_controller.rb
│ │ │ │ └── concerns/
│ │ │ │ └── .keep
│ │ │ ├── helpers/
│ │ │ │ └── application_helper.rb
│ │ │ ├── models/
│ │ │ │ └── concerns/
│ │ │ │ └── .keep
│ │ │ └── views/
│ │ │ └── layouts/
│ │ │ ├── application.html.erb
│ │ │ ├── mailer.html.erb
│ │ │ └── mailer.text.erb
│ │ ├── bin/
│ │ │ ├── bundle
│ │ │ ├── rails
│ │ │ ├── rake
│ │ │ ├── setup
│ │ │ ├── spring
│ │ │ ├── update
│ │ │ └── yarn
│ │ ├── config/
│ │ │ ├── application.rb
│ │ │ ├── barbeque.empty.yml
│ │ │ ├── barbeque.erb.yml
│ │ │ ├── barbeque.hako.yml
│ │ │ ├── barbeque.yml
│ │ │ ├── boot.rb
│ │ │ ├── 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
│ │ │ │ ├── permissions_policy.rb
│ │ │ │ ├── session_store.rb
│ │ │ │ └── wrap_parameters.rb
│ │ │ ├── locales/
│ │ │ │ └── en.yml
│ │ │ ├── puma.rb
│ │ │ ├── routes.rb
│ │ │ ├── secrets.yml
│ │ │ ├── spring.rb
│ │ │ └── storage.yml
│ │ ├── config.ru
│ │ ├── db/
│ │ │ ├── schema.rb
│ │ │ └── seeds.rb
│ │ ├── lib/
│ │ │ ├── assets/
│ │ │ │ └── .keep
│ │ │ └── tasks/
│ │ │ └── .keep
│ │ ├── log/
│ │ │ └── .keep
│ │ ├── public/
│ │ │ ├── 404.html
│ │ │ ├── 422.html
│ │ │ ├── 500.html
│ │ │ └── robots.txt
│ │ ├── tmp/
│ │ │ └── .keep
│ │ └── vendor/
│ │ └── assets/
│ │ ├── javascripts/
│ │ │ └── .keep
│ │ └── stylesheets/
│ │ └── .keep
│ ├── factories/
│ │ ├── app.rb
│ │ ├── job_definition.rb
│ │ ├── job_execution.rb
│ │ ├── job_queue.rb
│ │ ├── job_retry.rb
│ │ ├── retry_config.rb
│ │ ├── slack_notifications.rb
│ │ └── sns_subscription.rb
│ ├── models/
│ │ └── barbeque/
│ │ ├── job_definition_spec.rb
│ │ └── retry_config_spec.rb
│ ├── rails_helper.rb
│ ├── requests/
│ │ └── api/
│ │ ├── job_executions_spec.rb
│ │ ├── job_retries_spec.rb
│ │ └── revision_locks_spec.rb
│ ├── services/
│ │ └── message_enqueuing_service_spec.rb
│ └── spec_helper.rb
├── tools/
│ └── s3-log-migrator.rb
└── vendor/
└── assets/
├── javascripts/
│ ├── adminlte.js
│ ├── bootstrap.js
│ └── plotly-basic.js
└── stylesheets/
├── AdminLTE.css
├── bootstrap.css
└── skins/
└── skin-blue.css
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ruby:
- '3.0'
- '3.1'
- '3.2'
- '3.3'
name: Run test with Ruby ${{ matrix.ruby }}
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: barbeque_root
MYSQL_USER: barbeque
MYSQL_PASSWORD: barbeque
MYSQL_DATABASE: barbeque
ports:
- 3306:3306
options: >-
--health-cmd "mysqladmin ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
RAILS_ENV: test
DATABASE_URL: mysql2://barbeque:barbeque@127.0.0.1:3306/barbeque
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- run: bin/rails db:setup
- run: bin/rails zeitwerk:check
- run: bundle exec rspec
================================================
FILE: .gitignore
================================================
.bundle/
log/*.log
pkg/
Gemfile.lock
================================================
FILE: .rspec
================================================
--color
--require spec_helper
================================================
FILE: CHANGELOG.md
================================================
## v2.10.0 (2025-07-22)
### Changes
- Update Rails to 7.0
- Use `Bundler.with_unbundled_env` instead of deprecated `Bundler.with_clean_env`
## v2.9.0 (2025-01-24)
### Changes
- Update Rails to 6.1
- For Zeitwerk, Barbeque::SNSSubscription and Barbeque::SNSSubscriptionService are renamed to Barbeque::SnsSubscription and Barbeque::SnsSubscriptionService respectively
- Support Ruby 3.0
- Drop support for Ruby < 3.0
- Drop support for MySQL 5.7
- A new migration is added to fix collations properly in MySQL 8.0
## v2.8.0 (2021-12-23)
### New features
- Pass `BARBEQUE_SENT_TIMESTAMP` variable to invoked jobs
- The value is epoch time in milliseconds when the message is sent to the queue. See also: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ReceiveMessage.html
## v2.7.5 (2020-05-29)
### Bug fixes
- Use kaminari helper to generate links safely
## v2.7.4 (2020-04-08)
### Bug fixes
- Delete the same message multiple times when DeleteMessage results in partial deletion of copies
## v2.7.3 (2020-01-08)
### Bug fixes
- Accept retry configuration on create
## v2.7.2 (2019-11-05)
### Bug fixes
- Wrap JSON message in pre tag for large message
### Changes
- Change job name to case-sensitive
## v2.7.1 (2019-09-13)
### Bug fixes
- Do not count pending retried job executions when checking `maximum_concurrent_executions`
## v2.7.0 (2019-03-18)
### New features
- Add "Notify failure event to Slack only if retry limit reached" option
### Changes
- Change the default value of "Base delay" option from 0.3 seconds to 15 seconds
## v2.6.0 (2019-02-25)
### New features
- Add server-side retry feature
### Changes
- Stop deleting job executions when job definition is deleted
- Job executions tend to have large number of records, so deleting them is impossible.
- Return 503 in maintenance mode when mysql2 error occurs
- Use `BARBEQUE_HOST` environment variable to generate `html_url` field in API response
## v2.5.0 (2018-08-24)
### New features
- Add selectable `message` field to `GET /v1/job_executions/:message_id` response
- Add `BARBEQUE_VERIFY_ENQUEUED_JOBS` flag to API server which enables the feature that verifies the enqueued job by accessing MySQL
- Add `delay_seconds` parameter to support SQS's delay_seconds
- This also supports ActiveJob's enqueue_at method.
### Bug fixes
- Show all SNS topics in /sns_subscriptions/new
## v2.4.0 (2018-04-13)
### Changes
- Update Rails to 5.2
## v2.3.0 (2018-04-12)
### Changes
- Add index to barbeque_job_executions.created_at
- Be careful when you have large number of records in barbeque_job_executions table.
## v2.2.0 (2018-03-07)
### Changes
- Limit concurrent executions per job queue
- `maximum_concurrent_executions` was applied to all job executions regardless of job queues.
- Now `maximum_concurrent_executions` is applied to each job queue.
- Poll job executions and job retries only of the specified queue
- All execution pollers ware polling all job execution/retry statuses.
- Now execution pollers poll execution/retry statuses of their own job queue.
## v2.1.0 (2017-12-22)
### Improvements
- Support Hako definitions written in Jsonnet
- Jsonnet format is supported since Hako v2.0.0
- Rename yaml_dir to definition_dir in config/barbeque.yml
- yaml_dir is still supported with warnings for now
## v2.0.1 (2017-10-04)
### Improvements
- Build queue_url without database when maintenance mode is enabled
- See https://github.com/cookpad/barbeque/pull/58 for detail
## v2.0.0 (2017-09-19)
### Incompatibilities
- Job execution URL was changed from `/job_executions/:id` to `/job_executions/:message_id`
- Barbeque v1.0 links are redirected to v2.0 links
- Job retry URL `/job_executions/:id/job_retries/:id` is also redirected to `/job_executions/:message_id/job_retries/:id`
## v1.4.1 (2017-09-05)
### Bug fixes
- Do not create execution record when sqs:DeleteMessage returns error
## v1.4.0 (2017-08-31)
### Improvements
- Update aws-sdk to v3
- Use modularized aws-sdk gems
## v1.3.1 (2017-08-31)
### Improvements
- Filter job executions by status
## v1.3.0 (2017-08-21)
### New features
- Show SQS metrics in job queue page
### Improvements
- Update plotly.js to v1.29.3
### Bug fixes
- Do not truncate hover labels in /monitors chart
- Fix Slack notification field in job definition form
## v1.2.2 (2017-08-04)
### Improvements
- Extract S3 client for hako tasks
## v1.2.1 (2017-08-03)
### Bug fixes
- Do not create job_execution record when S3 returns error
- Ignore S3 errors when starting an execution
### Improvements
- Set descriptive title element
- Add breadcrumbs to all pages
## v1.2.0 (2017-07-26)
### Changes
- Update Rails to 5.1
## v1.1.0 (2017-07-25)
### Changes
- Add message context to exception handler
- Now exception handler is able to track which message is being processed when an exception is raised
### Bug fixes
- Set status to running after creating related records
## v1.0.0 (2017-07-24)
- Introduce Executor as a replacement of Runner
- `runner` and `runner_options` is renamed to `executor` and `executor_options` respectively
- Now `rake barbeque:worker` launches three types of process
- Runner: receives message from SQS queue, starts job execution and stores its identifier to the database
- In Executor::Docker, the identifier is container id
- In Executor::Hako, the identifier is ECS cluster and task ARN
- ExecutionPoller: polls execution status and reflect it to the database
- In Executor::Docker, uses `docker inspect` command
- In Executor::Docker, uses S3 task notification JSON
- RetryPoller: polls retry status and reflect it to the database
- Same with ExecutionPoller
- Add `maximum_concurrent_executions` configuration to config/barbeque.yml
- It controls the number of concurrent job executions
- The limit is disabled by default
- Drop support for legacy S3 log format
- Run [migration script](tools/s3-log-migrator.rb) before upgrading to v1.0.0
- Add `sqs_receive_message_wait_time` configuration to config/barbeque.yml
- This option controls ReceiveMessageWaitTimeSeconds attribute of SQS queue
- The default value is changed from 20s to 10s
## v0.7.0 (2017-07-12)
- Change S3 log format [#29](https://github.com/cookpad/barbeque/pull/29)
- The legacy format saves `{message: message.body.to_json, stdout: stdout, stderr: stderr}.to_json` to `#{app}/#{job}/#{message_id}`
- The new format saves message body to `#{app}/#{job}/#{message_id}/message.json`, stdout to `#{app}/#{job}/#{message_id}/stdout.txt`, and stderr to `#{app}/#{job}/#{message_id}/stderr.txt`
- The legacy format is still supported in v0.7.0, but will be removed in v1.0.0
- Migration script is available: [tools/s3-log-migrator.rb](tools/s3-log-migrator.rb)
## v0.6.3 (2017-07-10)
- Add "running" status [#28](https://github.com/cookpad/barbeque/pull/28)
## v0.6.2 (2017-06-06)
- Kill N+1 query [#27](https://github.com/cookpad/barbeque/pull/27)
## v0.6.1 (2017-06-06)
- Show application names for each job definition in SNS subscriptions [#26](https://github.com/cookpad/barbeque/pull/26)
## v0.6.0 (2017-06-05)
- Support JSON-formatted string as Notification massages [#25](https://github.com/cookpad/barbeque/pull/25)
## v0.5.2 (2017-05-23)
- Destroy SNS subscriptions before destroying job definition [#24](https://github.com/cookpad/barbeque/pull/24)
## v0.5.1 (2017-05-01)
- Log message body in error status for retry [#23](https://github.com/cookpad/barbeque/pull/23)
## v0.5.0 (2017-05-01)
- Add error status to job_execution [#22](https://github.com/cookpad/barbeque/pull/22)
## v0.4.1 (2017-04-28)
- Add error handling for AWS SNS API calls [#21](https://github.com/cookpad/barbeque/pull/21)
## v0.4.0 (2017-04-27)
- Support fan-out executions using AWS SNS notifications [#20](https://github.com/cookpad/barbeque/pull/20)
## v0.3.0 (2017-04-17)
- Fix job_retry order in job_execution page [#16](https://github.com/cookpad/barbeque/pull/16)
- Fix Back path to each job definition page [#17](https://github.com/cookpad/barbeque/pull/17)
- Fix "active" class in sidebar [#18](https://github.com/cookpad/barbeque/pull/18)
- Add new page to show recently processed jobs [#19](https://github.com/cookpad/barbeque/pull/19)
## v0.2.4 (2017-04-05)
- Autolink URLs in job_retry outputs [#15](https://github.com/cookpad/barbeque/pull/15)
## v0.2.3 (2017-03-24)
- Make operation to deduplicate messages atomic [#14](https://github.com/cookpad/barbeque/pull/14)
## v0.2.2 (2017-03-16)
- Add execution id and html_url to status API response [#13](https://github.com/cookpad/barbeque/pull/13)
## v0.2.1
- Fix bug in execution statistics [#12](https://github.com/cookpad/barbeque/pull/12)
## v0.2.0
- Add Hako runner [#11](https://github.com/cookpad/barbeque/pull/11)
## v0.1.0
- Handle S3 error on web console [#10](https://github.com/cookpad/barbeque/pull/10)
## v0.0.18
- Reuse AWS credentials assumed from Role [#9](https://github.com/cookpad/barbeque/pull/9)
## v0.0.17
- Move statistics button to upper right on job definition page
- Link app from job definitions index
## v0.0.16
- Autolink stdout and stderr [#8](https://github.com/cookpad/barbeque/pull/8)
## v0.0.15
- Report exception raised in SQS message parser
## v0.0.14
- Allow logging worker exception by Raven [#7](https://github.com/cookpad/barbeque/pull/7)
## v0.0.13
- Allow switching log output by `BARBEQUE_LOG_TO_STDOUT` [#6](https://github.com/cookpad/barbeque/pull/4)
## v0.0.12
- Destroy job definitions after their app destruction [#4](https://github.com/cookpad/barbeque/pull/4)
================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'
# Declare your gem's dependencies in barbeque.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
# development dependencies will be added by default to the :development group.
gemspec
# Declare any dependencies that are still in development here instead of in
# your gemspec. These might include edge Rails or gems from your path or
# Git. Remember to move these dependencies to your gemspec before releasing
# your gem to rubygems.org.
group :development, :test do
gem 'pry-byebug'
end
# Following gems don't work if they're required on spec_helper.
# It should be loaded on `Bundler.require` or `before_configuration`,
# and I don't want to to load them on `before_configuration` for test environment.
group :test do
gem 'rails-controller-testing'
end
# Workaround for https://github.com/rails/rails/pull/54264
gem 'concurrent-ruby', '< 1.3.5'
================================================
FILE: MIT-LICENSE
================================================
Copyright 2016 Takashi Kokubun
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# Barbeque [](https://github.com/cookpad/barbeque/actions/workflows/ci.yml)
Job queue system to run job with Docker
<img src="https://raw.githubusercontent.com/cookpad/barbeque/master/doc/images/job_definitions.png" height="280px" />
<img src="https://raw.githubusercontent.com/cookpad/barbeque/master/doc/images/statistics.png" height="280px" />
## Project Status
Barbeque is used on production at Cookpad.
## What's Barbeque?
Barbeque is a job queue system that consists of:
- Web console to manage jobs
- Web API to queue a job
- Worker to execute a job
A job for Barbeque is a command you configured on web console.
A message serialized by JSON and a job name are given to the command when performed.
In Barbeque worker, they are done on Docker container.
## Why Barbeque?
- You can achieve job-level auto scaling using tools like [Amazon ECS](https://aws.amazon.com/ecs/) [EC2 Auto Scaling group](https://aws.amazon.com/autoscaling/)
- For Amazon ECS, Barbeque has Hako executor
- You don't have to manage infrastructure for each application like Resque or Sidekiq
For details, see [Scalable Job Queue System Built with Docker // Speaker Deck](https://speakerdeck.com/k0kubun/scalable-job-queue-system-built-with-docker).
## Deployment
### Web API & console
Install barbeque.gem to an empty Rails app and mount `Barbeque::Engine`.
And deploy it as you like.
You also need to prepare MySQL, Amazon SQS and Amazon S3.
#### For sandbox environment
Barbeque's enqueue API tries to be independent of MySQL by design.
Although that design policy, verifying the enqueued job is useful in some environment (such as sandboxed environment).
Passing `BARBEQUE_VERIFY_ENQUEUED_JOBS=1` to the Web API server enables the feature that verifies the enqueued job by accessing MySQL.
### Worker
```bash
$ rake barbeque:worker BARBEQUE_QUEUE=default
```
The rake task launches four worker processes.
- Two runners
- receives message from SQS queue, starts job execution and stores its identifier to the database
- One execution poller
- gets execution status and reflect it to the database
- One retry poller
- gets retried execution status and reflect it to the database
## Usage
Web API documentation is available at [doc/toc.md](./doc/toc.md).
### Ruby
[barbeque\_client.gem](https://github.com/cookpad/barbeque_client) has API client and ActiveJob integration.
## Executor
Barbeque executor can be customized in config/barbeque.yml. Executor is responsible for starting executions and getting status of executions.
Barbeque has currently two executors.
### Docker (default)
Barbeque::Executor::Docker starts execution by `docker run --detach` and gets status by `docker inspect`.
### Hako
Barbeque::Executor::Hako starts execution by `hako oneshot --no-wait` and gets status from S3 task notification.
#### Requirement
You must configure CloudWatch Events for putting S3 task notification.
See Hako's documentation for detail.
https://github.com/eagletmt/hako/blob/master/docs/ecs-task-notification.md
## License
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
================================================
FILE: Rakefile
================================================
begin
require 'bundler/setup'
rescue LoadError
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
end
require 'rdoc/task'
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'Barbeque'
rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('README.md')
rdoc.rdoc_files.include('lib/**/*.rb')
end
require 'bundler/gem_tasks'
require File.expand_path('../spec/dummy/config/application', __FILE__)
namespace :plotly do
desc 'Update plotly.js to specified version'
task :update, [:version] do |t, args|
sh "curl -sSfL https://github.com/plotly/plotly.js/archive/v#{args[:version]}.tar.gz | tar zxf - plotly.js-#{args[:version]}/dist/plotly-basic.js -O > vendor/assets/javascripts/plotly-basic.js"
end
end
namespace :bootstrap do
desc 'Update Bootstrap to specified version'
task :update, [:version] do |t, args|
version = args.fetch(:version)
zipfile = "bootstrap-v#{version}.zip"
sh "curl -sSfL -o #{zipfile} https://github.com/twbs/bootstrap/releases/download/v#{version}/bootstrap-#{version}-dist.zip"
sh "bsdtar xf #{zipfile} --strip-components 2 -C vendor/assets/stylesheets bootstrap-#{version}-dist/css/bootstrap.css"
sh "bsdtar xf #{zipfile} --strip-components 2 -C vendor/assets/javascripts bootstrap-#{version}-dist/js/bootstrap.js"
end
end
namespace :adminlte do
desc 'Update AdminLTE to specified version'
task :update, [:version] do |t, args|
version = args.fetch(:version)
tarball = "adminlte-v#{version}.tar.gz"
sh "curl -sSfL -o #{tarball} https://github.com/ColorlibHQ/AdminLTE/archive/refs/tags/v#{version}.tar.gz"
sh "tar zxf #{tarball} --strip-components 3 -C vendor/assets/stylesheets AdminLTE-#{version}/dist/css/AdminLTE.css AdminLTE-#{version}/dist/css/skins/skin-blue.css"
sh "tar zxf #{tarball} --strip-components 3 -C vendor/assets/javascripts AdminLTE-#{version}/dist/js/adminlte.js"
end
end
Rails.application.load_tasks
================================================
FILE: app/assets/config/barbeque_manifest.js
================================================
//= link_directory ../javascripts/barbeque .js
//= link_directory ../stylesheets/barbeque .css
================================================
FILE: app/assets/images/barbeque/.keep
================================================
================================================
FILE: app/assets/javascripts/barbeque/application.js
================================================
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require bootstrap
//= require jquery_ujs
//= require adminlte
//= require plotly-basic
//= require_tree .
================================================
FILE: app/assets/javascripts/barbeque/job_definitions.js
================================================
jQuery(function($) {
if (!document.querySelector('.barbeque_job_definitions_controller')) {
return;
}
$('.use_slack_notification').bind('change', function(event) {
const enabledField = $('.slack_notification_field');
if (event.target.value === 'true') {
enabledField.removeClass('active');
} else {
enabledField.addClass('active');
}
});
$('.enable_retry_configuration').bind('change', function(event) {
const enabledField = $('.retry_configuration_field');
if (event.target.value === 'true') {
enabledField.removeClass('active');
} else {
enabledField.addClass('active');
}
});
});
================================================
FILE: app/assets/javascripts/barbeque/job_queues.js
================================================
jQuery(function($) {
if (!document.querySelector('.barbeque_job_queues_controller')) {
return;
}
const sqsDiv = document.getElementById('sqs-attributes');
if (!sqsDiv) {
return;
}
const { url, metricsUrl } = sqsDiv.dataset;
$.getJSON(url).done(data => {
renderBox(sqsDiv, 'SQS queue metrics', metricsUrl, data);
if (data.dlq) {
const dlqDiv = document.getElementById('sqs-dlq-attributes');
renderBox(dlqDiv, 'SQS dead-letter queue metrics', metricsUrl, data.dlq);
}
}).fail(jqxhr => {
const errorMessage = document.createElement('div');
errorMessage.classList.add('alert');
errorMessage.classList.add('alert-danger');
errorMessage.appendChild(document.createTextNode(`Server returned ${jqxhr.status}: ${jqxhr.statusText}`));
const indicator = sqsDiv.querySelector('.loading-indicator');
if (indicator) {
indicator.parentNode.removeChild(indicator);
}
sqsDiv.appendChild(errorMessage);
});
});
const renderBox = (div, title, metricsUrl, data) => {
const box = document.createElement('div');
box.classList.add('box');
const boxHeader = document.createElement('div');
boxHeader.classList.add('box-header');
const boxTitle = document.createElement('h3');
boxTitle.classList.add('box-title');
boxTitle.classList.add('with_padding');
boxTitle.appendChild(document.createTextNode(title));
boxHeader.appendChild(boxTitle);
const boxBody = document.createElement('div');
boxBody.classList.add('box-body');
box.appendChild(boxHeader);
box.appendChild(boxBody);
const table = document.createElement('table');
table.classList.add('table');
table.classList.add('table-bordered');
const thead = document.createElement('thead');
const theadTr = document.createElement('tr');
thead.appendChild(theadTr);
const tbody = document.createElement('tbody');
const tbodyTr = document.createElement('tr');
tbody.appendChild(tbodyTr);
for (const [name, value] of Object.entries(data.attributes)) {
const th = document.createElement('th');
th.appendChild(document.createTextNode(name));
theadTr.appendChild(th);
const td = document.createElement('td');
td.appendChild(document.createTextNode(value));
tbodyTr.appendChild(td);
}
table.appendChild(thead);
table.appendChild(tbody);
boxBody.appendChild(table);
const indicator = div.querySelector('.loading-indicator');
if (indicator) {
indicator.parentNode.removeChild(indicator);
}
div.appendChild(box);
const metrics = {
NumberOfMessagesSent: 'Sum',
ApproximateNumberOfMessagesVisible: 'Sum',
ApproximateNumberOfMessagesNotVisible: 'Sum',
ApproximateAgeOfOldestMessage: 'Maximum',
};
const row = document.createElement('div');
row.classList.add('row');
boxBody.appendChild(row);
for (const [metricName, statistic] of Object.entries(metrics)) {
$.getJSON(`${metricsUrl}?queue_name=${data.queue_name}&metric_name=${metricName}&statistic=${statistic}`).done(data => {
renderChart(row, data);
}).fail(jqxhr => {
const errorMessage = document.createElement('div');
errorMessage.classList.add('alert');
errorMessage.classList.add('alert-danger');
errorMessage.appendChild(document.createTextNode(`Failed to load SQS metrics ${metricName}: ${jqxhr.status}: ${jqxhr.statusText}`));
return div.appendChild(errorMessage);
});
}
};
const renderChart = function(row, data) {
const div = document.createElement('div');
div.classList.add('col-md-3');
const chartDiv = document.createElement('div');
div.appendChild(chartDiv);
div.dataset.label = data.label;
// Insert charts ordered by label name
let inserted = false;
for (const child of row.children) {
if (data.label < child.dataset.label) {
row.insertBefore(div, child);
inserted = true;
break;
}
}
if (!inserted) {
row.appendChild(div);
}
return Plotly.plot(chartDiv, [{
type: 'scatter',
x: data.datapoints.map(point => point.timestamp),
y: data.datapoints.map(point => point.value),
}], {
title: data.label,
});
};
================================================
FILE: app/assets/stylesheets/barbeque/application.css
================================================
/*
*= require bootstrap
*= require AdminLTE
*= require skins/skin-blue
*= require barbeque/job_definitions
*/
:root {
--barbeque-border-color: #ececec;
}
.box-body {
padding: 12px;
}
.box-header .box-title.with_padding {
padding: 6px;
font-size: 22px;
}
.table.table-bordered {
margin-bottom: 12px;
border-color: var(--barbeque-border-color);
}
.table.table-bordered td, .table.table-bordered th {
border-color: var(--barbeque-border-color);
}
================================================
FILE: app/assets/stylesheets/barbeque/job_definitions.css
================================================
.barbeque_job_definitions_controller .use_slack_notification_wrapper label {
font-weight: normal;
}
.barbeque_job_definitions_controller .use_slack_notification {
margin: 0 2px 0 8px;
}
.barbeque_job_definitions_controller .slack_notification_field {
display: none;
}
.barbeque_job_definitions_controller .slack_notification_field label {
font-weight: normal;
}
.barbeque_job_definitions_controller .slack_notification_field.active {
display: block;
}
.barbeque_job_definitions_controller .retry_configuration_wrapper label {
font-weight: normal;
}
.barbeque_job_definitions_controller .enable_retry_configuration {
margin: 0 2px 0 8px;
}
.barbeque_job_definitions_controller .retry_configuration_field {
display: none;
}
.barbeque_job_definitions_controller .retry_configuration_field label {
font-weight: normal;
}
.barbeque_job_definitions_controller .retry_configuration_field.active {
display: block;
}
.barbeque_job_definitions_controller .table.table-bordered td,
.barbeque_job_definitions_controller .table.table-bordered th {
overflow-wrap: anywhere;
min-width: 100px;
}
================================================
FILE: app/controllers/barbeque/api/application_controller.rb
================================================
require 'garage'
class Barbeque::Api::ApplicationController < ActionController::API
before_action :force_json_format
include Garage::ControllerHelper
rescue_from ActiveRecord::RecordNotFound do |exception|
respond_with_error(404, 'record_not_found', exception.message)
end
rescue_from WeakParameters::ValidationError do |exception|
respond_with_error(400, 'invalid_parameter', exception.message)
end
private
# @param [Integer] status_code HTTP status code
# @param [String] error_code Must be unique
# @param [String] message Error message for API client, not for end user.
def respond_with_error(status_code, error_code, message)
render json: { status_code: status_code, error_code: error_code, message: message }, status: status_code
end
# This is required to use ActionController::API with Garage
def force_json_format
request.format = :json
end
end
================================================
FILE: app/controllers/barbeque/api/job_executions_controller.rb
================================================
require 'barbeque/maintenance'
class Barbeque::Api::JobExecutionsController < Barbeque::Api::ApplicationController
include Garage::RestfulActions
validates :create do
string :application, required: true, description: 'Application name of the job'
string :job, required: true, description: 'Class of Job to be enqueued'
string :queue, required: true, description: 'Queue name to enqueue a job'
any :message, required: true, description: 'Free-format JSON'
integer :delay_seconds, description: 'Set message timer of SQS'
end
rescue_from Barbeque::MessageEnqueuingService::BadRequest do |exc|
render status: 400, json: { error: exc.message }
end
private
def require_resources
protect_resource_as Barbeque::Api::JobExecutionResource
end
def require_resource
model = Barbeque::JobExecution.find_or_initialize_by(message_id: params[:message_id])
@resource = Barbeque::Api::JobExecutionResource.new(model)
rescue ActiveRecord::StatementInvalid, Mysql2::Error::ConnectionError => e
if Barbeque::Maintenance.database_maintenance_mode?
Barbeque::ExceptionHandler.handle_exception(e)
@resource = Barbeque::Api::DatabaseMaintenanceResource.new(e)
else
raise e
end
end
def create_resource
message_id = enqueue_message
model = Barbeque::JobExecution.new(message_id: message_id)
@resource = Barbeque::Api::JobExecutionResource.new(model)
end
# @return [String] id of a message queued to SQS.
def enqueue_message
Barbeque::MessageEnqueuingService.new(
application: params[:application],
job: params[:job],
queue: params[:queue],
message: params[:message],
delay_seconds: params[:delay_seconds],
).run
end
# Link to job_execution isn't available if it isn't dequeued yet
def location
if @resource.id
super
else
nil
end
end
def respond_with_resource_options
if @resource.is_a?(Barbeque::Api::DatabaseMaintenanceResource)
super.merge(status: 503)
else
super
end
end
end
================================================
FILE: app/controllers/barbeque/api/job_retries_controller.rb
================================================
class Barbeque::Api::JobRetriesController < Barbeque::Api::ApplicationController
include Garage::RestfulActions
# http://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html
SQS_MAX_DELAY_SECONDS = 900
validates :create do
integer :delay_seconds, only: 0..SQS_MAX_DELAY_SECONDS
end
private
def require_resources
protect_resource_as Barbeque::Api::JobRetryResource
end
def create_resource
result = retry_message
Barbeque::JobRetry.new(message_id: result.message_id).to_resource
end
def retry_message
Barbeque::MessageRetryingService.new(
message_id: params[:job_execution_message_id],
delay_seconds: params[:delay_seconds].to_i,
).run
end
end
================================================
FILE: app/controllers/barbeque/api/revision_locks_controller.rb
================================================
class Barbeque::Api::RevisionLocksController < Barbeque::Api::ApplicationController
include Garage::RestfulActions
validates :create do
string :revision, required: true, description: 'Docker image revision to lock'
end
private
def require_resources
protect_resource_as Barbeque::Api::RevisionLockResource
end
def require_resource
@resource = Barbeque::App.find_by!(name: params[:app_name])
end
def create_resource
app = Barbeque::App.find_by!(name: params[:app_name])
image = Barbeque::DockerImage.new(app.docker_image)
image.tag = params[:revision]
app.update!(docker_image: image.to_s)
Barbeque::Api::RevisionLockResource.new(app)
end
def destroy_resource
image = Barbeque::DockerImage.new(@resource.docker_image)
image.tag = 'latest'
@resource.update!(docker_image: image.to_s)
Barbeque::Api::RevisionLockResource.new(@resource)
end
end
================================================
FILE: app/controllers/barbeque/application_controller.rb
================================================
module Barbeque
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
end
================================================
FILE: app/controllers/barbeque/apps_controller.rb
================================================
class Barbeque::AppsController < Barbeque::ApplicationController
def index
@apps = Barbeque::App.all
end
def show
@app = Barbeque::App.find(params[:id])
end
def new
@app = Barbeque::App.new
end
def edit
@app = Barbeque::App.find(params[:id])
end
def create
@app = Barbeque::App.new(params.require(:app).permit(:name, :docker_image, :description))
if @app.save
redirect_to @app, notice: 'App was successfully created.'
else
render :new
end
end
def update
@app = Barbeque::App.find(params[:id])
# Name can't be changed after it's created.
if @app.update(params.require(:app).permit(:docker_image, :description))
redirect_to @app, notice: 'App was successfully updated.'
else
render :edit
end
end
def destroy
@app = Barbeque::App.find(params[:id])
@app.destroy
redirect_to root_path, notice: 'App was successfully destroyed.'
end
end
================================================
FILE: app/controllers/barbeque/job_definitions_controller.rb
================================================
class Barbeque::JobDefinitionsController < Barbeque::ApplicationController
def index
@job_definitions = Barbeque::JobDefinition.all
end
def show
@job_definition = Barbeque::JobDefinition.find(params[:id])
@job_executions = @job_definition.job_executions.order(id: :desc).page(params[:page])
@retry_config = @job_definition.retry_config
@status = params[:status].presence.try(&:to_i)
if @status
@job_executions = @job_executions.where(status: @status)
end
end
def new
@job_definition = Barbeque::JobDefinition.new
@job_definition.build_slack_notification
@job_definition.build_retry_config
if params[:job_definition]
@job_definition.assign_attributes(new_job_definition_params)
end
end
def edit
@job_definition = Barbeque::JobDefinition.find(params[:id])
unless @job_definition.slack_notification
@job_definition.build_slack_notification
end
unless @job_definition.retry_config
@job_definition.build_retry_config
end
end
def create
attributes = new_job_definition_params.merge(command: command_array)
@job_definition = Barbeque::JobDefinition.new(attributes)
if @job_definition.save
redirect_to @job_definition, notice: 'Job definition was successfully created.'
else
render :new
end
end
def update
@job_definition = Barbeque::JobDefinition.find(params[:id])
attributes = params.require(:job_definition).permit(
:description,
slack_notification_attributes: slack_notification_params,
retry_config_attributes: retry_config_params,
).merge(command: command_array)
if @job_definition.update(attributes)
redirect_to @job_definition, notice: 'Job definition was successfully updated.'
else
render :edit
end
end
def destroy
@job_definition = Barbeque::JobDefinition.find(params[:id])
@job_definition.sns_subscriptions.each do |sns_subscription|
Barbeque::SnsSubscriptionService.new.unsubscribe(sns_subscription)
end
@job_definition.destroy
redirect_to job_definitions_url, notice: 'Job definition was successfully destroyed.'
end
def stats
@job_definition = Barbeque::JobDefinition.find(params[:job_definition_id])
@days = (params[:days] || 3).to_i
end
def execution_stats
job_definition = Barbeque::JobDefinition.find(params[:job_definition_id])
days = (params[:days] || 3).to_i
now = Time.zone.now
render json: job_definition.execution_stats(days.days.ago(now), now)
end
private
def slack_notification_params
%i[id channel notify_success notify_failure_only_if_retry_limit_reached failure_notification_text _destroy]
end
def retry_config_params
%i[id retry_limit base_delay max_delay jitter _destroy]
end
def command_array
Shellwords.split(params.require(:job_definition)[:command])
end
def new_job_definition_params
params.require(:job_definition).permit(
:job,
:app_id,
:description,
slack_notification_attributes: slack_notification_params,
retry_config_attributes: retry_config_params,
)
end
end
================================================
FILE: app/controllers/barbeque/job_executions_controller.rb
================================================
class Barbeque::JobExecutionsController < Barbeque::ApplicationController
ID_REGEXP = /\A[0-9]+\z/
def show
if ID_REGEXP === params[:message_id]
job_execution = Barbeque::JobExecution.find_by(id: params[:message_id])
if job_execution
redirect_to(job_execution)
return
end
end
@job_execution = Barbeque::JobExecution.find_by!(message_id: params[:message_id])
# Return 404 when job_definition or app is deleted
@job_definition = Barbeque::JobDefinition.find(@job_execution.job_definition_id)
@app = Barbeque::App.find(@job_definition.app_id)
@log = @job_execution.execution_log
@job_retries = @job_execution.job_retries.order(id: :desc)
end
def retry
@job_execution = Barbeque::JobExecution.find_by!(message_id: params[:job_execution_message_id])
raise ActionController::BadRequest unless @job_execution.retryable?
result = Barbeque::MessageRetryingService.new(message_id: @job_execution.message_id).run
@job_execution.retried!
redirect_to @job_execution, notice: "Succeed to retry (message_id=#{result.message_id})"
end
end
================================================
FILE: app/controllers/barbeque/job_queues_controller.rb
================================================
require 'aws-sdk-cloudwatch'
require 'aws-sdk-sqs'
require 'barbeque/config'
class Barbeque::JobQueuesController < Barbeque::ApplicationController
def index
@job_queues = Barbeque::JobQueue.all
end
def show
@job_queue = Barbeque::JobQueue.find(params[:id])
end
def new
@job_queue = Barbeque::JobQueue.new
end
def edit
@job_queue = Barbeque::JobQueue.find(params[:id])
end
def create
@job_queue = Barbeque::JobQueue.new(params.require(:job_queue).permit(:name, :description))
@job_queue.queue_url = create_queue(@job_queue).queue_url if @job_queue.valid?
if @job_queue.save
redirect_to @job_queue, notice: 'Job queue was successfully created.'
else
render :new
end
end
def update
@job_queue = Barbeque::JobQueue.find(params[:id])
# Name can't be changed after it's created.
if @job_queue.update(params.require(:job_queue).permit(:description))
redirect_to @job_queue, notice: 'Job queue was successfully updated.'
else
render :edit
end
end
def destroy
@job_queue = Barbeque::JobQueue.find(params[:id])
@job_queue.destroy
redirect_to job_queues_url, notice: 'Job queue was successfully destroyed.'
end
def sqs_attributes
job_queue = Barbeque::JobQueue.find(params[:id])
attributes = self.class.sqs_client.get_queue_attributes(
queue_url: job_queue.queue_url,
attribute_names: %w[
ApproximateNumberOfMessages
ApproximateNumberOfMessagesNotVisible
RedrivePolicy
QueueArn
],
).attributes
dlq_metrics =
if attributes['RedrivePolicy']
dlq_arn = JSON.parse(attributes['RedrivePolicy']).fetch('deadLetterTargetArn')
dlq_name = queue_name_from_arn(dlq_arn)
dlq_url = self.class.sqs_client.get_queue_url(queue_name: dlq_name).queue_url
dlq_attributes = self.class.sqs_client.get_queue_attributes(
queue_url: dlq_url,
attribute_names: %w[
ApproximateNumberOfMessages
ApproximateNumberOfMessagesNotVisible
],
).attributes.transform_values(&:to_i)
{
queue_name: dlq_name,
attributes: dlq_attributes,
}
else
nil
end
render json: {
queue_name: queue_name_from_arn(attributes['QueueArn']),
attributes: {
'ApproximateNumberOfMessages' => attributes['ApproximateNumberOfMessages'].to_i,
'ApproximateNumberOfMessagesNotVisible' => attributes['ApproximateNumberOfMessagesNotVisible'].to_i,
},
dlq: dlq_metrics,
}
end
def sqs_metrics
queue_name = params[:queue_name]
unless queue_name.present?
render status: 400, json: { error: 'params[:queue_name] is required' }
return
end
metric_name = params[:metric_name]
unless metric_name.present?
render status: 400, json: { error: 'params[:metric_name] is required' }
return
end
statistic = params[:statistic]
unless statistic.present?
render status: 400, json: { error: 'params[:statistic] is required' }
return
end
now = Time.zone.now
from = now - 24.hours
resp = self.class.cloudwatch_client.get_metric_statistics(
namespace: 'AWS/SQS',
metric_name: metric_name,
dimensions: [
{ name: 'QueueName', value: queue_name },
],
start_time: from,
end_time: now,
period: compute_minimum_period(from, now),
statistics: [statistic],
)
render json: {
label: resp.label,
datapoints: resp.datapoints.sort_by(&:timestamp).map { |datapoint|
{
timestamp: Time.zone.at(datapoint.timestamp),
value: datapoint[statistic.underscore],
}
},
}
end
private
# @return [Aws::SQS::Types::CreateQueueResult] A struct which has only queue_url.
def create_queue(job_queue)
Aws::SQS::Client.new.create_queue(
queue_name: job_queue.sqs_queue_name,
attributes: {
'ReceiveMessageWaitTimeSeconds' => Barbeque.config.sqs_receive_message_wait_time.to_s,
},
)
end
def self.sqs_client
@sqs_client ||= Aws::SQS::Client.new
end
def self.cloudwatch_client
@cloudwatch_client ||= Aws::CloudWatch::Client.new
end
def queue_name_from_arn(arn)
arn.slice(/[^:]+\z/)
end
def compute_minimum_period(start_time, end_time, maximum_datapoint: 1440)
# - Datapoints cannot exceed 1,440
# - Period must be a multiple of 60
maximum_datapoint = [maximum_datapoint, 1440].min.to_f
minimum_period = ((end_time - start_time) / maximum_datapoint).ceil
r = minimum_period % 60
if r == 0
minimum_period
else
minimum_period - r + 60
end
end
end
================================================
FILE: app/controllers/barbeque/job_retries_controller.rb
================================================
class Barbeque::JobRetriesController < Barbeque::ApplicationController
def show
@job_retry = Barbeque::JobRetry.find(params[:id])
@job_execution = @job_retry.job_execution
# Return 404 when job_definition or app is deleted
@job_definition = Barbeque::JobDefinition.find(@job_execution.job_definition_id)
@app = Barbeque::App.find(@job_definition.app_id)
if params[:job_execution_message_id] != @job_execution.message_id
redirect_to([@job_execution, @job_retry])
end
@execution_log = @job_execution.execution_log
@retry_log = @job_retry.execution_log
end
end
================================================
FILE: app/controllers/barbeque/monitors_controller.rb
================================================
class Barbeque::MonitorsController < Barbeque::ApplicationController
def index
now = Time.zone.now
from = 6.hours.ago(now.beginning_of_hour)
rows = Barbeque::JobExecution.find_by_sql([<<SQL.strip_heredoc, from, now]).map(&:attributes)
select
t.date_hour
, app.id as app_id
, app.name as app_name
, def.id as job_id
, def.job as job_name
, t.cnt
from
(
select
date_format(e.created_at, '%Y-%m-%d %H:00:00') as date_hour
, e.job_definition_id
, count(1) as cnt
from #{Barbeque::JobExecution.table_name} e
where
e.created_at between ? and ?
group by
date_hour
, e.job_definition_id
) t
inner join #{Barbeque::JobDefinition.table_name} def on def.id = t.job_definition_id
inner join #{Barbeque::App.table_name} app on app.id = def.app_id
SQL
jobs = {}
rows.each do |row|
job_id = row.fetch('job_id')
job = {
app_id: row.fetch('app_id'),
app_name: row.fetch('app_name'),
job_id: job_id,
job_name: row.fetch('job_name'),
}
jobs[job_id] = job
end
@recently_processed_jobs = {}
t = from
while t < now
@recently_processed_jobs[t] = {}
jobs.each do |job_id, job|
@recently_processed_jobs[t][job_id] = job.merge(count: 0)
end
t += 1.hour
end
rows.each do |row|
date_hour = Time.zone.parse("#{row.fetch('date_hour')} UTC")
job_id = row.fetch('job_id')
@recently_processed_jobs[date_hour][job_id] = jobs[job_id].merge(count: row.fetch('cnt'))
end
end
end
================================================
FILE: app/controllers/barbeque/sns_subscriptions_controller.rb
================================================
class Barbeque::SnsSubscriptionsController < Barbeque::ApplicationController
def index
@sns_subscriptions = Barbeque::SnsSubscription.all
end
def show
@sns_subscription = Barbeque::SnsSubscription.find(params[:id])
end
def new
@sns_subscription = Barbeque::SnsSubscription.new
end
def edit
@sns_subscription = Barbeque::SnsSubscription.find(params[:id])
end
def create
@sns_subscription = Barbeque::SnsSubscription.new(params.require(:sns_subscription).permit(:topic_arn, :job_queue_id, :job_definition_id))
if Barbeque::SnsSubscriptionService.new.subscribe(@sns_subscription)
redirect_to @sns_subscription, notice: 'SNS subscription was successfully created.'
else
render :new
end
end
def update
@sns_subscription = Barbeque::SnsSubscription.find(params[:id])
if @sns_subscription.update(params.require(:sns_subscription).permit(:job_definition_id))
redirect_to @sns_subscription, notice: 'SNS subscription was successfully updated.'
else
render :edit
end
end
def destroy
sns_subscription = Barbeque::SnsSubscription.find(params[:id])
Barbeque::SnsSubscriptionService.new.unsubscribe(sns_subscription)
redirect_to sns_subscriptions_path, notice: 'SNS subscription was successfully destroyed.'
end
end
================================================
FILE: app/helpers/barbeque/application_helper.rb
================================================
module Barbeque
module ApplicationHelper
end
end
================================================
FILE: app/helpers/barbeque/job_definitions_helper.rb
================================================
module Barbeque::JobDefinitionsHelper
def distance_of_time(from, to)
return '' if from.nil? || to.nil?
secs = (to - from).to_i
mins = secs / 60
hours = mins / 60
days = hours / 24
text = "%02d:%02d:%02d" % [hours % 24, mins % 60, secs % 60]
text.prepend("#{days}days ") if days > 0
text
end
end
================================================
FILE: app/helpers/barbeque/job_executions_helper.rb
================================================
module Barbeque::JobExecutionsHelper
def status_label(status)
color =
case status
when 'success'
'success'
when 'failed'
'danger'
when 'retried'
'warning'
when 'pending'
'info'
when 'error'
'danger'
when 'running'
'info'
else
'default'
end
content_tag(:span, status.upcase, class: "label label-#{color}")
end
end
================================================
FILE: app/models/barbeque/api/application_resource.rb
================================================
require 'garage'
class Barbeque::Api::ApplicationResource
include Garage::Representer
include Garage::Authorizable
attr_reader :model
def initialize(model = nil)
@model = model
end
end
================================================
FILE: app/models/barbeque/api/database_maintenance_resource.rb
================================================
class Barbeque::Api::DatabaseMaintenanceResource
include Garage::Representer
property :message
delegate :message, to: :@exception
def initialize(exception)
@exception = exception
end
end
================================================
FILE: app/models/barbeque/api/job_execution_resource.rb
================================================
class Barbeque::Api::JobExecutionResource < Barbeque::Api::ApplicationResource
property :message_id
property :status
property :id
property :html_url, selectable: true
property :message, selectable: true
delegate :message_id, :status, :id, to: :model
def html_url
if model.id
Barbeque::Engine.routes.url_helpers.job_execution_url(model, host: ENV['BARBEQUE_HOST'])
else
nil
end
end
def message
log = @model.execution_log
if log
log['message']
end
end
end
================================================
FILE: app/models/barbeque/api/job_retry_resource.rb
================================================
class Barbeque::Api::JobRetryResource < Barbeque::Api::ApplicationResource
property :message_id
property :status
delegate :message_id, :status, to: :model
end
================================================
FILE: app/models/barbeque/api/revision_lock_resource.rb
================================================
class Barbeque::Api::RevisionLockResource < Barbeque::Api::ApplicationResource
property :revision
def revision
Barbeque::DockerImage.new(@model.docker_image).tag
end
end
================================================
FILE: app/models/barbeque/app.rb
================================================
class Barbeque::App < Barbeque::ApplicationRecord
validates :name, presence: true, uniqueness: true
validates :docker_image, presence: true
attr_readonly :name
has_many :job_definitions, dependent: :destroy
end
================================================
FILE: app/models/barbeque/application_record.rb
================================================
module Barbeque
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
end
================================================
FILE: app/models/barbeque/docker_container.rb
================================================
class Barbeque::DockerContainer < Barbeque::ApplicationRecord
end
================================================
FILE: app/models/barbeque/ecs_hako_task.rb
================================================
class Barbeque::EcsHakoTask < Barbeque::ApplicationRecord
end
================================================
FILE: app/models/barbeque/job_definition.rb
================================================
class Barbeque::JobDefinition < Barbeque::ApplicationRecord
belongs_to :app
has_many :job_executions
has_many :sns_subscriptions
has_one :slack_notification, dependent: :destroy
has_one :retry_config, dependent: :destroy
validates :job, uniqueness: { scope: :app_id }
attr_readonly :app_id
attr_readonly :job
serialize :command, Array
accepts_nested_attributes_for :slack_notification, allow_destroy: true
accepts_nested_attributes_for :retry_config, allow_destroy: true
DATE_HOUR_SQL = 'date_format(created_at, "%Y-%m-%d %H:00:00")'
def execution_stats(from, to)
from = from.beginning_of_hour
to = to.beginning_of_hour
stats = Hash.new { |h, k| h[k] = { count: 0, avg_time: 0 } }
job_executions.where(created_at: from .. to).group(DATE_HOUR_SQL).order(Arel.sql(DATE_HOUR_SQL)).pluck(Arel.sql("#{DATE_HOUR_SQL}, count(1), avg(timestampdiff(second, created_at, finished_at))")).each do |date_hour, count, avg_time|
time = Time.zone.parse("#{date_hour} UTC")
stats[time] = {
count: count,
avg_time: avg_time,
}
end
(from.to_i ... to.to_i).step(1.hour.to_i).map do |t|
time = Time.at(t)
stats[time].merge(date_hour: time)
end
end
end
================================================
FILE: app/models/barbeque/job_execution.rb
================================================
class Barbeque::JobExecution < Barbeque::ApplicationRecord
belongs_to :job_definition
belongs_to :job_queue
has_one :slack_notification, through: :job_definition
has_one :app, through: :job_definition
has_many :job_retries, dependent: :destroy
enum status: {
pending: 0,
success: 1,
failed: 2,
retried: 3,
error: 4,
running: 5,
}
paginates_per 15
# @return [Hash] - A hash created by `JobExecutor::Job#log_result`
def execution_log
@execution_log ||= Barbeque::ExecutionLog.load(execution: self)
end
def retryable?
failed? || error?
end
def to_param
message_id
end
def retry_if_possible!
unless retryable?
return
end
retry_config = job_definition.retry_config
unless retry_config
return
end
retries = job_retries.count
if retry_config.should_retry?(retries)
delay_seconds = retry_config.delay_seconds(retries).to_i
Barbeque::MessageRetryingService.new(message_id: message_id, delay_seconds: delay_seconds).run
retried!
end
end
end
================================================
FILE: app/models/barbeque/job_queue.rb
================================================
require 'barbeque/maintenance'
class Barbeque::JobQueue < Barbeque::ApplicationRecord
SQS_NAME_PREFIX = ENV['BARBEQUE_SQS_NAME_PREFIX'] || 'Barbeque-'
SQS_NAME_MAX_LENGTH = 80
has_many :job_executions
has_many :sns_subscriptions, dependent: :destroy
# SQS queue allows [a-zA-Z0-9_-]+ as queue name. Its maximum length is 80.
validates :name, presence: true, uniqueness: true, format: /\A[a-zA-Z0-9_-]+\z/,
length: { maximum: SQS_NAME_MAX_LENGTH - SQS_NAME_PREFIX.length }
attr_readonly :name
def sqs_queue_name
SQS_NAME_PREFIX + name
end
# Returns queue URL of given name.
# Basically, we should use stored queue URL as the documentation[1] suggests.
# But when the Barbeque's database is temporarily unavailable due to
# scheduled maintenance, we have to build queue URL without the database. The
# maintenance mode is enabled by BARBEQUE_DATABASE_MAINTENANCE and
# AWS_ACCOUNT_ID variable.
# [1]: http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-queue-message-identifiers.html#sqs-general-identifiers
#
# @param name [String] queue name in Barbeque
# @return [String] queue URL of SQS
def self.queue_url_from_name(name)
if Barbeque::Maintenance.database_maintenance_mode?
"https://sqs.#{ENV.fetch('AWS_REGION')}.amazonaws.com/#{ENV.fetch('AWS_ACCOUNT_ID')}/#{SQS_NAME_PREFIX}#{name}"
else
select(:queue_url).find_by!(name: name).queue_url
end
end
end
================================================
FILE: app/models/barbeque/job_retry.rb
================================================
class Barbeque::JobRetry < Barbeque::ApplicationRecord
belongs_to :job_execution
has_one :job_definition, through: :job_execution
has_one :app, through: :job_definition
has_one :slack_notification, through: :job_execution
enum status: {
pending: 0,
success: 1,
failed: 2,
retried: 3,
error: 4,
running: 5,
}
# @return [Hash] - A hash created by `JobExecutor::Retry#log_result`
def execution_log
@execution_log ||= Barbeque::ExecutionLog.load(execution: self)
end
def to_resource
Barbeque::Api::JobRetryResource.new(self)
end
end
================================================
FILE: app/models/barbeque/retry_config.rb
================================================
class Barbeque::RetryConfig < Barbeque::ApplicationRecord
belongs_to :job_definition
validates :retry_limit, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
validates :base_delay, numericality: { greater_than: 0.0 }, allow_nil: true
validates :max_delay, numericality: { greater_than: 0 }, allow_nil: true
def should_retry?(retries)
retries < retry_limit
end
# This algorithm is based on "Exponential Backoff And Jitter" article
# https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
def delay_seconds(retries)
delay = 2 ** retries * base_delay
if max_delay
delay = [delay, max_delay].min
end
if jitter
delay = Kernel.rand(0 .. delay)
end
delay
end
end
================================================
FILE: app/models/barbeque/slack_notification.rb
================================================
class Barbeque::SlackNotification < Barbeque::ApplicationRecord
belongs_to :job_definition, optional: true
validates :channel, presence: true
end
================================================
FILE: app/models/barbeque/sns_subscription.rb
================================================
module Barbeque
class SnsSubscription < ApplicationRecord
belongs_to :job_queue
belongs_to :job_definition
has_one :app, through: :job_definition
validates :topic_arn,
uniqueness: { scope: :job_queue, message: 'should be set with only one queue' },
presence: true
validate :topic_arn_is_formatted
def topic_region
Aws::ARNParser.parse(topic_arn).region
end
private
def topic_arn_is_formatted
unless Aws::ARNParser.arn?(topic_arn)
errors.add(:topic_arn, 'is not a valid ARN')
end
end
end
end
================================================
FILE: app/models/concerns/.keep
================================================
================================================
FILE: app/services/barbeque/message_enqueuing_service.rb
================================================
require 'aws-sdk-sqs'
class Barbeque::MessageEnqueuingService
DEFAULT_QUEUE = ENV['BARBEQUE_DEFAULT_QUEUE'] || 'default'
VERIFY_ENQUEUED_JOBS = ENV['BARBEQUE_VERIFY_ENQUEUED_JOBS'] || '0'
class BadRequest < StandardError
end
def self.sqs_client
@sqs_client ||= Aws::SQS::Client.new
end
# @param [String] application
# @param [String] job
# @param [Object] message
# @param [String] queue
# @param [Integer, nil] delay_seconds
def initialize(application:, job:, message:, queue: nil, delay_seconds: nil)
@application = application
@job = job
@queue = queue || DEFAULT_QUEUE
@message = message
@delay_seconds = delay_seconds
end
# @return [String] message_id
def run
queue_url = Barbeque::JobQueue.queue_url_from_name(@queue)
if VERIFY_ENQUEUED_JOBS == '1'
unless Barbeque::JobDefinition.joins(:app).merge(Barbeque::App.where(name: @application)).where(job: @job).exists?
raise BadRequest.new("JobDefinition '#{@job}' isn't defined in '#{@application}' application")
end
end
response = Barbeque::MessageEnqueuingService.sqs_client.send_message(
queue_url: queue_url,
message_body: build_message.to_json,
delay_seconds: @delay_seconds,
)
response.message_id
rescue Aws::SQS::Errors::InvalidParameterValue => e
raise BadRequest.new(e.message)
end
private
def build_message
{
'Type' => 'JobExecution',
'Application' => @application,
'Job' => @job,
'Message' => @message,
}
end
end
================================================
FILE: app/services/barbeque/message_retrying_service.rb
================================================
require 'aws-sdk-sqs'
class Barbeque::MessageRetryingService
DEFAULT_DELAY_SECONDS = 0
def self.sqs_client
@sqs_client ||= Aws::SQS::Client.new
end
def initialize(message_id:, delay_seconds: nil)
@message_id = message_id
@delay_seconds = delay_seconds || DEFAULT_DELAY_SECONDS
end
def run
execution = Barbeque::JobExecution.find_by!(message_id: @message_id)
Barbeque::MessageRetryingService.sqs_client.send_message(
queue_url: execution.job_queue.queue_url,
message_body: build_message.to_json,
delay_seconds: @delay_seconds,
)
end
private
def build_message
{
'Type' => 'JobRetry',
'RetryMessageId' => @message_id,
}
end
end
================================================
FILE: app/services/barbeque/sns_subscription_service.rb
================================================
require 'aws-sdk-sns'
require 'aws-sdk-sqs'
class Barbeque::SnsSubscriptionService
def self.sqs_client
@sqs_client ||= Aws::SQS::Client.new
end
def self.sns_client(region:)
return @sns_client[region] if @sns_client
@sns_client = Hash.new { |hash, region| hash[region] = Aws::SNS::Client.new(region: region) }
@sns_client[region]
end
# @param [Barbeque::SnsSubscription] sns_subscription
# @return [Boolean] `true` if succeeded to subscribe
def subscribe(sns_subscription)
if sns_subscription.valid?
begin
subscribe_topic!(sns_subscription)
sns_subscription.save!
update_sqs_policy!(sns_subscription)
true
rescue Aws::SNS::Errors::AuthorizationError
sns_subscription.errors.add(:topic_arn, 'is not authorized')
false
rescue Aws::SNS::Errors::NotFound
sns_subscription.errors.add(:topic_arn, 'is not found')
false
end
else
false
end
end
# @param [Barbeque::SnsSubscription] sns_subscription
def unsubscribe(sns_subscription)
sns_subscription.destroy
update_sqs_policy!(sns_subscription)
unsubscribe_topic!(sns_subscription)
nil
end
private
def sqs_client
Barbeque::SnsSubscriptionService.sqs_client
end
def sns_client(region:)
Barbeque::SnsSubscriptionService.sns_client(region: region)
end
# @param [Barbeque::SnsSubscription] sns_subscription
def update_sqs_policy!(sns_subscription)
attrs = sqs_client.get_queue_attributes(
queue_url: sns_subscription.job_queue.queue_url,
attribute_names: ['QueueArn'],
)
queue_arn = attrs.attributes['QueueArn']
topic_arns = sns_subscription.job_queue.sns_subscriptions.map(&:topic_arn)
if topic_arns.present?
policy = generate_policy(queue_arn: queue_arn, topic_arns: topic_arns)
else
policy = '' # Be blank when there're no subscriptions.
end
sqs_client.set_queue_attributes(
queue_url: sns_subscription.job_queue.queue_url,
attributes: { 'Policy' => policy },
)
end
# @param [String] queue_arn
# @param [Array<String>] topic_arns
# @return [String] JSON formatted policy
def generate_policy(queue_arn:, topic_arns:)
{
'Version' => '2012-10-17',
'Statement' => [
'Effect' => 'Allow',
'Principal' => '*',
'Action' => 'sqs:SendMessage',
'Resource' => queue_arn,
'Condition' => {
'ArnEquals' => {
'aws:SourceArn' => topic_arns,
}
}
]
}.to_json
end
# @param [Barbeque::SnsSubscription] sns_subscription
def subscribe_topic!(sns_subscription)
sqs_attrs = sqs_client.get_queue_attributes(
queue_url: sns_subscription.job_queue.queue_url,
attribute_names: ['QueueArn'],
)
queue_arn = sqs_attrs.attributes['QueueArn']
sns_client(region: sns_subscription.topic_region).subscribe(
topic_arn: sns_subscription.topic_arn,
protocol: 'sqs',
endpoint: queue_arn
)
end
# @param [Barbeque::SnsSubscription] sns_subscription
def unsubscribe_topic!(sns_subscription)
sqs_attrs = sqs_client.get_queue_attributes(
queue_url: sns_subscription.job_queue.queue_url,
attribute_names: ['QueueArn'],
)
queue_arn = sqs_attrs.attributes['QueueArn']
region = sns_subscription.topic_region
subscriptions = sns_client(region: region).list_subscriptions_by_topic(
topic_arn: sns_subscription.topic_arn,
)
subscription_arn = subscriptions.subscriptions.find {|subscription| subscription.endpoint == queue_arn }.try!(:subscription_arn)
if subscription_arn
sns_client(region: region).unsubscribe(
subscription_arn: subscription_arn,
)
end
end
end
================================================
FILE: app/views/barbeque/apps/_form.html.haml
================================================
.box.box-primary
.box-header
%h3.box-title.with_padding
#{action_name.capitalize} Application
.box-body
= form_for @app do |f|
- if @app.errors.any?
%strong #{pluralize(@app.errors.count, 'error')} prohibited this application from being saved:
%ul
- @app.errors.full_messages.each do |msg|
%li= msg
.row.form-group
.col-md-4
= f.label :name
- if @app.persisted?
-# Name can't be changed after it's created.
.app_name= @app.name
- else
= f.text_field :name, class: 'form-control'
.row.form-group
.col-md-4
= f.label :docker_image
= f.text_field :docker_image, class: 'form-control'
.row.form-group
.col-md-8
= f.label :description
= f.text_area :description, class: 'form-control', rows: 10
.form-group
= f.submit 'Save', class: 'btn btn-primary'
================================================
FILE: app/views/barbeque/apps/edit.html.haml
================================================
- content_for(:title, "Edit #{@app.name} - Barbeque")
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to(@app.name, app_path(@app.id))
= render 'form'
= link_to 'Back', app_path(@app.id)
================================================
FILE: app/views/barbeque/apps/index.html.haml
================================================
- content_for(:title, 'Barbeque')
- content_for(:header) do
%ol.breadcrumb
%li.active Home
.box.box-primary
.box-header
%h3.box-title.with_padding
All Applications
= link_to new_app_path, class: 'btn btn-primary pull-right' do
New Application
.box-body
%table.table.table-bordered
%thead
%tr
%th Name
%th Docker image
%th Description
%th
%tbody
- @apps.each do |app|
%tr
%td= app.name
%td= app.docker_image
%td= app.description
%td
= link_to 'View Details', app, class: 'btn btn-default btn-sm'
================================================
FILE: app/views/barbeque/apps/new.html.haml
================================================
- content_for(:title, 'New application - Barbeque')
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
= render 'form'
= link_to 'Back', root_path
================================================
FILE: app/views/barbeque/apps/show.html.haml
================================================
- content_for(:title, "#{@app.name} - Barbeque")
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li.active #{@app.name}
.box.box-primary
.box-header
%h3.box-title.with_padding
Application Details
.box-body
%p#notice= notice
%table.table.table-bordered
%tbody
%tr
%th Name
%td= @app.name
%tr
%th Docker image
%td= @app.docker_image
%tr
%th Description
%td= @app.description
= link_to 'Edit', edit_app_path(@app), class: 'btn btn-primary'
= link_to 'Destroy', app_path(@app),
class: 'btn', method: :delete, data: { confirm: 'Are you sure to delete application?' }
.box
.box-header
%h3.box-title.with_padding Job Definitions
= link_to 'New Job Definition', new_job_definition_path(job_definition: { app_id: @app.id }), class: 'btn btn-primary pull-right'
.box-body
%table.table.table-bordered
%thead
%tr
%th Application
%th Job
%th Description
%th
%tbody
- @app.job_definitions.each do |job_definition|
%tr
%td= job_definition.app.name
%td= job_definition.job
%td= job_definition.description
%td
= link_to 'View Details', job_definition, class: 'btn btn-default btn-sm'
= link_to 'Back', root_path
================================================
FILE: app/views/barbeque/job_definitions/_form.html.haml
================================================
.box.box-primary
.box-header
%h3.box-title.with_padding
#{action_name.capitalize} Job Definition
.box-body
= form_for @job_definition do |f|
- if @job_definition.errors.any?
%strong #{pluralize(@job_definition.errors.count, 'error')} prohibited this job_definition from being saved:
%ul
- @job_definition.errors.full_messages.each do |msg|
%li= msg
.row.form-group
.col-md-4
= f.label :app_id
- if @job_definition.persisted?
.job_definition_app_name= @job_definition.app.name
- else
= f.collection_select :app_id, Barbeque::App.pluck(:id, :name), :first, :second,
{ prompt: true }, class: 'form-control'
.row.form-group
.col-md-4
= f.label :job
- if @job_definition.persisted?
.job_definition_job= @job_definition.job
- else
= f.text_field :job, class: 'form-control', placeholder: 'SomeAsyncJob'
.row.form-group
.col-md-8
= f.label :command
= f.text_field :command, value: Shellwords.join(@job_definition.command),
class: 'form-control', placeholder: 'bundle exec rake ...'
.row.form-group
.col-md-8
= f.label :description
= f.text_area :description, class: 'form-control', rows: 10
= render 'slack_notification_field', job_definition: @job_definition
= render 'retry_configuration_field', job_definition: @job_definition
.form-group
= f.submit 'Save', class: 'btn btn-primary'
================================================
FILE: app/views/barbeque/job_definitions/_retry_configuration_field.html.haml
================================================
= fields_for(job_definition) do |job_definition_f|
= job_definition_f.fields_for(:retry_config) do |f|
= f.hidden_field :id
.row
.col-md-8
%label Retry configuration
- retry_enabled = f.object.persisted?
.row.form-group
.col-md-8.retry_configuration_wrapper
= f.label :_destroy_true do
= f.radio_button :_destroy, true, class: 'enable_retry_configuration', checked: !retry_enabled
No
= f.label :_destroy_false do
= f.radio_button :_destroy, false, class: 'enable_retry_configuration', checked: retry_enabled
Yes
.retry_configuration_field{ class: ('active' if retry_enabled) }
.row.form-group
.col-md-4
= f.label :retry_limit
= f.number_field :retry_limit, min: 1, class: 'form-control'
.row.form-group
.col-md-4
= f.label :base_delay
= f.number_field :base_delay, min: 0.1, step: 0.1, class: 'form-control'
.row.form-group
.col-md-4
= f.label :max_delay
= f.number_field :max_delay, min: 1, class: 'form-control'
.row.form-group
.col-md-4
= f.label :jitter
= f.check_box :jitter
================================================
FILE: app/views/barbeque/job_definitions/_slack_notification_field.html.haml
================================================
= fields_for job_definition do |job_definition_f|
= job_definition_f.fields_for :slack_notification do |f|
= f.hidden_field :id
.row
.col-md-8
%label Slack Notification
- use_slack_notification = f.object.persisted?
.row.form-group
.col-md-8.use_slack_notification_wrapper
= f.label :_destroy_true do
= f.radio_button :_destroy, true, class: 'use_slack_notification', checked: !use_slack_notification
No
= f.label :_destroy_false do
= f.radio_button :_destroy, false, class: 'use_slack_notification', checked: use_slack_notification
Yes
.slack_notification_field{ class: ('active' if use_slack_notification) }
.row.form-group
.col-md-8
= f.text_field :channel, placeholder: '#slack-channel', class: 'form-control'
.row.form-group
.col-md-8
= f.check_box :notify_success
= f.label :notify_success, 'Notify success event to Slack'
.col-md-8
= f.check_box :notify_failure_only_if_retry_limit_reached
= f.label :notify_failure_only_if_retry_limit_reached, 'Notify failure event to Slack only if retry limit reached'
.row
.col-md-8
Failure notification text
.row.form-group
.col-md-8
= f.text_field :failure_notification_text,
placeholder: '@YourName', class: 'form-control'
================================================
FILE: app/views/barbeque/job_definitions/edit.html.haml
================================================
- content_for(:title, "Edit job definition #{@job_definition.job} - Barbeque")
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to(@job_definition.app.name, app_path(@job_definition.app.id))
%li= link_to(@job_definition.job, job_definition_path(@job_definition.id))
= render 'form'
= link_to 'Back', job_definition_path(@job_definition)
================================================
FILE: app/views/barbeque/job_definitions/index.html.haml
================================================
- content_for(:title, 'Job definitions - Barbeque')
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
.box.box-primary
.box-header
%h3.box-title.with_padding
All Job Definitions
= link_to new_job_definition_path, class: 'btn btn-primary pull-right' do
New Job Definition
.box-body
%table.table.table-bordered
%thead
%tr
%th Application
%th Job
%th Description
%th
%tbody
- @job_definitions.each do |job_definition|
%tr
%td= link_to job_definition.app.name, app_path(job_definition.app.id)
%td= job_definition.job
%td= job_definition.description
%td
= link_to 'View Details', job_definition, class: 'btn btn-default btn-sm'
================================================
FILE: app/views/barbeque/job_definitions/new.html.haml
================================================
- content_for(:title, 'New job definition - Barbeque')
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
- if @job_definition.app
%li= link_to(@job_definition.app.name, app_path(@job_definition.app.id))
= render 'form'
= link_to 'Back', job_definitions_path
================================================
FILE: app/views/barbeque/job_definitions/show.html.haml
================================================
- content_for(:title, "#{@job_definition.job} - Barbeque")
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to(@job_definition.app.name, app_path(@job_definition.app.id))
%li.active #{@job_definition.job}
.row
.col-sm-7
.box.box-primary
.box-header
%h3.box-title.with_padding
Job Definition Details
.box-body
%p#notice= notice
%table.table.table-bordered
%tbody
%tr
%th Application
%td= link_to(@job_definition.app.name, app_path(@job_definition.app.id))
%tr
%th Job
%td= @job_definition.job
%tr
%th Command
%td= Shellwords.join(@job_definition.command)
%tr
%th Description
%td= @job_definition.description
%tr
%th Slack Notification
%td
- if @job_definition.slack_notification
Yes (#{@job_definition.slack_notification.channel})
- else
No
%tr
%th Retry configuration
%td
- if @retry_config
Yes (retry_limit=#{@retry_config.retry_limit} base_delay=#{@retry_config.base_delay} max_delay=#{@retry_config.max_delay || '+inf'} jitter=#{@retry_config.jitter ? 'enabled' : 'disabled'})
- else
No
= link_to 'Edit', edit_job_definition_path(@job_definition), class: 'btn btn-primary'
= link_to 'Destroy', job_definition_path(@job_definition),
class: 'btn', method: :delete, data: { confirm: 'Are you sure to delete job definition?' }
.col-sm-5
.box.box-primary
.box-header
%h3.box-title.with_padding.pull-left
Job Executions
= link_to(job_definition_stats_path(@job_definition), class: 'btn btn-default pull-right') do
%i.fa.fa-line-chart
Statistics
.box-body
.nav-tabs-custom
%ul.nav.nav-tabs
%li.dropdown
%a.dropdown-toggle{href: '#', data: { toggle: 'dropdown' }}
Filter by status
%span.caret
%ul.dropdown-menu
- Barbeque::JobExecution.statuses.each do |name, value|
%li{role: 'presentation'}
= link_to(url_for(status: value), role: 'menuitem', class: 'active') do
- if value == @status
%b= name
- else
= name
- if @job_executions.present?
%table.table.table-bordered
%thead
%tr
%th ID
%th Status
%th Started At
%th Elapsed Time
%th
%tbody
- @job_executions.each do |job_execution|
%tr
%td= job_execution.id
%td= status_label(job_execution.status)
%td= l(job_execution.created_at, format: :short)
%td= distance_of_time(job_execution.created_at, job_execution.finished_at)
%td
.btn-group
= link_to job_execution_path(job_execution), class: 'btn btn-default btn-sm btn-flat' do
%i.fa.fa-chevron-right
Details
- if job_execution.retryable?
= link_to job_execution_retry_path(job_execution), method: :post, class: 'btn btn-default btn-sm btn-flat',
data: { disable_with: 'retrying...', confirm: "Are you sure to retry #{@job_definition.job} ##{job_execution.id}?" } do
%i.fa.fa-refresh
Retry
.row
.col-xs-4
= link_to path_to_prev_page(@job_executions),
class: "btn btn-default btn-sm btn-block #{'disabled' unless @job_executions.prev_page}" do
%i.fa.fa-angle-left
New
.col-xs-4.col-xs-offset-4
= link_to path_to_next_page(@job_executions),
class: "btn btn-default btn-sm btn-block #{'disabled' unless @job_executions.next_page}" do
Old
%i.fa.fa-angle-right
- else
No job execution
= link_to 'Back', job_definitions_path
================================================
FILE: app/views/barbeque/job_definitions/stats.html.haml
================================================
- content_for(:title, "#{@job_definition.job} statistics - Barbeque")
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to(@job_definition.app.name, app_path(@job_definition.app.id))
%li= link_to(@job_definition.job, job_definition_path(@job_definition.id))
.row
.col-sm-12
.box.box-primary
.box-header
%h3.box-title.with_padding
%i.fa.fa-line-chart
Execution statistics for #{@days} #{'day'.pluralize(@days)}
.box-body
= form_tag(job_definition_stats_path(@job_definition), method: :get, class: 'form-inline') do
.form-group
.input-group
= text_field_tag :days, @days, class: 'form-control'
.input-group-addon
days
= submit_tag 'Go', class: 'btn btn-default'
#execution-count-chart
#execution-time-chart
= link_to 'Back', job_definition_path(@job_definition)
:javascript
jQuery(function($) {
$.getJSON('#{job_definition_execution_stats_path(@job_definition, days: @days)}').then(stats => {
const countDiv = document.getElementById('execution-count-chart');
const timeDiv = document.getElementById('execution-time-chart');
const date_hours = stats.map(stat => stat.date_hour);
const counts = stats.map(stat => stat.count);
const avg_times = stats.map(stat => stat.avg_time);
Plotly.plot(countDiv, [
{
type: 'scatter',
name: 'Number of executions',
x: date_hours,
y: counts,
},
], {
title: 'Number of executions (hourly)',
});
Plotly.plot(timeDiv, [
{
type: 'scatter',
name: 'Average execution time',
x: date_hours,
y: avg_times,
},
], {
title: 'Average execution time (hourly)',
});
});
});
================================================
FILE: app/views/barbeque/job_executions/show.html.haml
================================================
- content_for(:title, "Job execution ##{@job_execution.id} of #{@job_definition.job} - Barbeque")
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to(@app.name, app_path(@app.id))
%li= link_to(@job_definition.job, job_definition_path(@job_definition.id))
%li.active #{@job_execution.message_id}
.row
.col-sm-7
.box.box-primary
.box-header
.row
.col-md-10
%h3.box-title.with_padding
\##{@job_execution.id} of
= link_to @job_definition do
#{@job_definition.job}
- if @job_execution.retryable?
.col-md-2
= link_to job_execution_retry_path(@job_execution), method: :post, class: 'btn btn-default btn-block pull-right',
data: { disable_with: 'retrying...', confirm: "Are you sure to retry #{@job_definition.job} ##{@job_execution.id}?" } do
Retry
.box-body
%table.table.table-bordered
%tbody
%tr
%th Execution ID
%td= @job_execution.id
%tr
%th Status
%td= status_label(@job_execution.status)
%tr
%th Created at
%td= @job_execution.created_at
%tr
%th Finished at
%td= @job_execution.finished_at
%tr
%th Elapsed time
%td= distance_of_time(@job_execution.created_at, @job_execution.finished_at)
%tr
%th Message ID
%td= @job_execution.message_id
%tr
%th Message
%td
- if @log
%pre
%code= @log['message']
- else
Log was not found.
.col-sm-5
.box.box-primary
.box-header
%h3.box-title.with_padding
Retries
.box-body
- if @job_retries.present?
%table.table.table-bordered
%thead
%tr
%th ID
%th Status
%th Started At
%th Elapsed Time
%th
%tbody
- @job_retries.each do |job_retry|
%tr
%td= job_retry.id
%td= status_label(job_retry.status)
%td= l(job_retry.created_at, format: :short)
%td= distance_of_time(job_retry.created_at, job_retry.finished_at)
%td
.btn-group
= link_to job_execution_job_retry_path(@job_execution, job_retry), class: 'btn btn-default btn-sm btn-flat' do
%i.fa.fa-chevron-right
Details
- else
Not retried.
.row
.col-sm-6
.box.box-primary
.box-header
%h3.box-title.with_padding
Stdout
.box-body
- if @log
%pre= Rinku.auto_link(html_escape(@log['stdout'])).html_safe
- else
Log was not found.
.col-sm-6
.box.box-primary
.box-header
%h3.box-title.with_padding
Stderr
.box-body
- if @log
%pre= Rinku.auto_link(html_escape(@log['stderr'])).html_safe
- else
Log was not found.
= link_to 'Back', job_definition_path(@job_definition)
================================================
FILE: app/views/barbeque/job_queues/_form.html.haml
================================================
.box.box-primary
.box-header
%h3.box-title.with_padding
#{action_name.capitalize} Job Queue
.box-body
= form_for @job_queue do |f|
- if @job_queue.errors.any?
%strong #{pluralize(@job_queue.errors.count, 'error')} prohibited this job_queue from being saved:
%ul
- @job_queue.errors.full_messages.each do |msg|
%li= msg
.row.form-group
.col-md-4
= f.label :name
- if @job_queue.persisted?
-# Name can't be changed after it's created.
.job_queue_name= @job_queue.name
- else
= f.text_field :name, class: 'form-control'
.row.form-group
.col-md-8
= f.label :description
= f.text_area :description, class: 'form-control', rows: 10
.form-group
= f.submit 'Save', class: 'btn btn-primary'
================================================
FILE: app/views/barbeque/job_queues/edit.html.haml
================================================
- content_for(:title, "Edit #{@job_queue.name} job queue - Barbeque")
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to('Job queues', job_queues_path)
%li= link_to(@job_queue.name, job_queue_path(@job_queue.id))
= render 'form'
= link_to 'Back', job_queue_path(@job_queue.id)
================================================
FILE: app/views/barbeque/job_queues/index.html.haml
================================================
- content_for(:title, 'Job queues - Barbeque')
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li.active Job queues
.box.box-primary
.box-header
%h3.box-title.with_padding
All Job Queues
= link_to new_job_queue_path, class: 'btn btn-primary pull-right' do
New Job Queue
.box-body
%table.table.table-bordered
%thead
%tr
%th Name
%th Description
%th
%tbody
- @job_queues.each do |job_queue|
%tr
%td= job_queue.name
%td= job_queue.description
%td
= link_to 'View Details', job_queue, class: 'btn btn-default btn-sm'
================================================
FILE: app/views/barbeque/job_queues/new.html.haml
================================================
- content_for(:title, 'New job queue - Barbeque')
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to('Job queues', job_queues_path)
= render 'form'
= link_to 'Back', job_queues_path
================================================
FILE: app/views/barbeque/job_queues/show.html.haml
================================================
- content_for(:title, "#{@job_queue.name} job queue - Barbeque")
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to('Job queues', job_queues_path)
%li.active= @job_queue.name
.box.box-primary
.box-header
%h3.box-title.with_padding
Job Queue Details
.box-body
%p#notice= notice
%table.table.table-bordered
%tbody
%tr
%th Name
%td= @job_queue.name
%tr
%th Description
%td= @job_queue.description
= link_to 'Edit', edit_job_queue_path(@job_queue), class: 'btn btn-primary'
= link_to 'Destroy', job_queue_path(@job_queue),
class: 'btn', method: :delete, data: { confirm: 'Are you sure to delete queue?' }
#sqs-attributes{data: {url: sqs_attributes_job_queue_url(@job_queue.id), metrics_url: sqs_metrics_job_queues_url}}
%i.loading-indicator.fa.fa-spinner.fa-spin
#sqs-dlq-attributes
= link_to 'Back', job_queues_path
================================================
FILE: app/views/barbeque/job_retries/show.html.haml
================================================
- content_for(:title, "Job retry ##{@job_retry.id} of #{@job_definition.job} - Barbeque")
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to(@app.name, app_path(@app.id))
%li= link_to(@job_definition.job, job_definition_path(@job_definition.id))
%li= link_to(@job_execution.message_id, job_execution_path(@job_execution))
%li.active ##{@job_retry.id}
.row
.col-sm-12
.box.box-primary
.box-header
.row
.col-md-10
%h3.box-title.with_padding
Retry ##{@job_retry.id} of
= link_to @job_definition do
#{@job_definition.job}
= link_to @job_execution do
\##{@job_execution.id}
.box-body
%table.table.table-bordered
%tbody
%tr
%th Execution ID
%td= @job_retry.id
%tr
%th Status
%td= status_label(@job_retry.status)
%tr
%th Created at
%td= @job_retry.created_at
%tr
%th Finished at
%td= @job_retry.finished_at
%tr
%th Elapsed time
%td= distance_of_time(@job_retry.created_at, @job_retry.finished_at)
%tr
%th Message ID
%td= @job_retry.message_id
%tr
%th Message
%td
- if @execution_log
%pre
%code= @execution_log['message']
- else
Execution log was not found.
.row
.col-sm-6
.box.box-primary
.box-header
%h3.box-title.with_padding
Stdout
.box-body
- if @retry_log
%pre= Rinku.auto_link(html_escape(@retry_log['stdout'])).html_safe
- else
Retry log was not found.
.col-sm-6
.box.box-primary
.box-header
%h3.box-title.with_padding
Stderr
.box-body
- if @retry_log
%pre= Rinku.auto_link(html_escape(@retry_log['stderr'])).html_safe
- else
Retry log was not found.
= link_to 'Back', job_execution_path(@job_execution)
================================================
FILE: app/views/barbeque/monitors/index.html.haml
================================================
- content_for(:title, 'Monitors - Barbeque')
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li.active Monitors
.row
.col-sm-12
.box.box-primary
.box-header
%h3.box-title.with_padding Recently processed jobs (hourly)
.box-body
#recently-processed-jobs-chart{data: { jobs: @recently_processed_jobs.to_json }}
%table.table
%thead
%tr
%th Hour
%th Application
%th Job
%th Count
%tbody
- @recently_processed_jobs.keys.sort { |x, y| y <=> x }.each do |date_hour|
- jobs = @recently_processed_jobs[date_hour]
- jobs.keys.sort.each do |job_id|
- job = jobs[job_id]
%tr
%td= date_hour
%td= link_to(job[:app_name], app_path(job[:app_id]))
%td= link_to(job[:job_name], job_definition_path(job[:job_id]))
%td= job[:count]
:javascript
const chartDiv = document.getElementById('recently-processed-jobs-chart');
const recentlyProcesedJobs = JSON.parse(chartDiv.dataset.jobs);
const countsPerJob = {};
for (const [dateHour, jobs] of Object.entries(recentlyProcesedJobs)) {
for (const job of Object.values(jobs)) {
const name = job.app_name + ' - ' + job.job_name;
if (!countsPerJob[name]) { countsPerJob[name] = []; }
if (!countsPerJob[name][dateHour]) { countsPerJob[name][dateHour] = job.count; }
}
}
const plotlyArgs = [];
for (const [name, series] of Object.entries(countsPerJob)) {
const x = [];
const y = [];
for (const [dateHour, count] of Object.entries(series)) {
x.push(dateHour);
y.push(count);
}
plotlyArgs.push({
type: 'scatter',
name,
x,
y,
hoverlabel: {
namelength: -1,
}
});
}
Plotly.plot(chartDiv, plotlyArgs);
================================================
FILE: app/views/barbeque/sns_subscriptions/_form.html.haml
================================================
.box.box-primary
.box-header
%h3.box-title.with_padding
#{action_name.capitalize} SNS Subscription
.box-body
= form_for @sns_subscription do |f|
- if @sns_subscription.errors.any?
%strong #{pluralize(@sns_subscription.errors.count, 'error')} prohibited this SNS subscription from being saved:
%ul
- @sns_subscription.errors.full_messages.each do |msg|
%li= msg
.row.form-group
.col-md-8
= f.label :topic_arn
- if @sns_subscription.persisted?
.sns_subscription_topic_arn= @sns_subscription.topic_arn
- else
= f.text_field :topic_arn, class: 'form-control'
.row.form-group
.col-md-4
= f.label :job_queue_id
- if @sns_subscription.persisted?
.sns_subscription_job_queue_id= link_to @sns_subscription.job_queue.name, @sns_subscription.job_queue
- else
= f.collection_select :job_queue_id, Barbeque::JobQueue.pluck(:id, :name), :first, :second,
{ prompt: true }, class: 'form-control'
.row.form-group
.col-md-4
= f.label :job_definition_id
= f.collection_select :job_definition_id, Barbeque::JobDefinition.includes(:app).all.map {|definition| [definition.id, "#{definition.app.name} - #{definition.job}"] }, :first, :second,
{ prompt: true }, class: 'form-control'
.form-group
= f.submit 'Save', class: 'btn btn-primary'
================================================
FILE: app/views/barbeque/sns_subscriptions/edit.html.haml
================================================
- content_for(:title, "Edit SNS subscription between #{@sns_subscription.topic_arn} and #{@sns_subscription.job_queue.name} - Barbeque")
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to('SNS subscriptions', sns_subscriptions_path)
%li= link_to(@sns_subscription.topic_arn, sns_subscription_path(@sns_subscription.id))
= render 'form'
= link_to 'Back', sns_subscription_path(@sns_subscription)
================================================
FILE: app/views/barbeque/sns_subscriptions/index.html.haml
================================================
- content_for(:title, 'SNS subscriptions - Barbeque')
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li.active SNS subscriptions
.box.box-primary
.box-header
%h3.box-title.with_padding
All SNS Subscriptions
= link_to new_sns_subscription_path, class: 'btn btn-primary pull-right' do
New SNS Subscriptions
.box-body
%table.table.table-bordered
%thead
%tr
%th topic_arn
%th Job Queue
%th Job Definition
%th
%tbody
- @sns_subscriptions.each do |sns_subscription|
%tr
%td= sns_subscription.topic_arn
%td= sns_subscription.job_queue.name
%td= sns_subscription.job_definition.job
%td
= link_to 'View Details', sns_subscription, class: 'btn btn-default btn-sm'
================================================
FILE: app/views/barbeque/sns_subscriptions/new.html.haml
================================================
- content_for(:title, 'New SNS subscription - Barbeque')
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to('SNS subscriptions', sns_subscriptions_path)
= render 'form'
= link_to 'Back', sns_subscriptions_path
================================================
FILE: app/views/barbeque/sns_subscriptions/show.html.haml
================================================
- content_for(:title, "SNS subscription between #{@sns_subscription.topic_arn} and #{@sns_subscription.job_queue.name} - Barbeque")
- content_for(:header) do
%ol.breadcrumb
%li= link_to('Home', root_path)
%li= link_to('SNS subscriptions', sns_subscriptions_path)
%li.active #{@sns_subscription.topic_arn}
.row
.col-sm-7
.box.box-primary
.box-header
%h3.box-title.with_padding
SNS Subscription Details
.box-body
%p#notice= notice
%table.table.table-bordered
%tbody
%tr
%th topic_arn
%td= @sns_subscription.topic_arn
%tr
%th Job Queue
%td= link_to @sns_subscription.job_queue.name, @sns_subscription.job_queue
%tr
%th Job Defenition
%td= link_to @sns_subscription.job_definition.job, @sns_subscription.job_definition
%tr
= link_to 'Edit', edit_sns_subscription_path(@sns_subscription), class: 'btn btn-primary'
= link_to 'Destroy', sns_subscription_path(@sns_subscription),
class: 'btn', method: :delete, data: { confirm: 'Are you sure to delete SNS subscription?' }
= link_to 'Back', sns_subscriptions_path
================================================
FILE: app/views/layouts/barbeque/_header.html.haml
================================================
%header.main-header
%a.logo{ href: '/' }
%span.logo-mini
%b> BBQ
%span.logo-lg
%b> Barbeque
%nav.navbar.navbar-static-top{ role: 'navigation' }
%a.sidebar-toggle{ 'data-toggle': 'offcanvas', href: '#', role: 'button' }
%span.sr-only Toggle navigation
================================================
FILE: app/views/layouts/barbeque/_sidebar.html.haml
================================================
%aside.main-sidebar
%section.sidebar
%ul.sidebar-menu
%li.header Menu
%li{ class: ('active' if controller_path == 'barbeque/apps') }
= link_to root_path do
%i.fa.fa-users
%span Applications
%li{ class: ('active' if controller_path == 'barbeque/job_definitions') }
= link_to job_definitions_path do
%i.fa.fa-tasks
%span Job Definitions
%li{ class: ('active' if controller_path == 'barbeque/job_queues') }
= link_to job_queues_path do
%i.fa.fa-cubes
%span Queues
%li{ class: ('active' if controller_path == 'barbeque/sns_subscriptions') }
= link_to sns_subscriptions_path do
%i.fa.fa-calendar-plus-o
%span SNS Subscriptions
%li{ class: ('active' if controller_path == 'barbeque/monitors') }
= link_to monitors_path do
%i.fa.fa-tachometer
%span Monitors
================================================
FILE: app/views/layouts/barbeque/application.html.haml
================================================
!!!
%html
%head
%meta{ content: 'text/html; charset=UTF-8', 'http-equiv': 'Content-Type' }
%meta{ charset: 'UTF-8' }
%title= content_for(:title)
%meta{ content: 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no', name: 'viewport' }
-# FIXME: Don't rely on others' CDN
%link{ href: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css', rel: 'stylesheet', type: 'text/css' }
%link{ href: 'https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css', rel: 'stylesheet', type: 'text/css' }
-# https://github.com/ColorlibHQ/AdminLTE/commit/235481d1d6324c1d4257f86c621700aa7f4a0b7f
%link{ href: 'https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic', rel: 'stylesheet', type: 'text/css' }
= stylesheet_link_tag 'barbeque/application', media: 'all'
= javascript_include_tag 'barbeque/application'
= csrf_meta_tags
%body.skin-blue.sidebar-mini{ class: "#{controller_path.parameterize(separator: '_')}_controller #{action_name}_action" }
.wrapper
= render partial: 'layouts/barbeque/header'
= render partial: 'layouts/barbeque/sidebar'
.content-wrapper
- if content_for?(:header)
%section.content-header
= content_for(:header)
%section.content
= yield
================================================
FILE: app/views/layouts/barbeque/apps.html.haml
================================================
- content_for(:header) do
%h1
.fa.fa-users
Applications
= render template: 'layouts/barbeque/application'
================================================
FILE: app/views/layouts/barbeque/job_definitions.html.haml
================================================
- content_for(:header) do
%h1
.fa.fa-tasks
Job Definitions
= render template: 'layouts/barbeque/application'
================================================
FILE: app/views/layouts/barbeque/job_executions.html.haml
================================================
- content_for(:header) do
%h1
Job Executions
= render template: 'layouts/barbeque/application'
================================================
FILE: app/views/layouts/barbeque/job_queues.html.haml
================================================
- content_for(:header) do
%h1
.fa.fa-cubes
Queues
= render template: 'layouts/barbeque/application'
================================================
FILE: app/views/layouts/barbeque/job_retries.html.haml
================================================
- content_for(:header) do
%h1
Job Retries
= render template: 'layouts/barbeque/application'
================================================
FILE: app/views/layouts/barbeque/monitors.html.haml
================================================
- content_for(:header) do
%h1
.fa.fa-tachometer
Monitors
= render template: 'layouts/barbeque/application'
================================================
FILE: app/views/layouts/barbeque/sns_subscriptions.html.haml
================================================
- content_for(:header) do
%h1
%i.fa.fa-calendar-plus-o
SNS Subscriptions
= render template: 'layouts/barbeque/application'
================================================
FILE: barbeque.gemspec
================================================
$:.push File.expand_path("../lib", __FILE__)
require "barbeque/version"
Gem::Specification.new do |s|
s.name = "barbeque"
s.version = Barbeque::VERSION
s.authors = ["Takashi Kokubun"]
s.email = ["takashi-kokubun@cookpad.com"]
s.homepage = "https://github.com/cookpad/barbeque"
s.summary = "Job queue system to run job with Docker"
s.description = "Job queue system to run job with Docker"
s.license = "MIT"
s.files = Dir["{app,config,db,lib,vendor}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
s.required_ruby_version = '>= 3.0.0'
s.add_dependency "aws-sdk-cloudwatch"
s.add_dependency "aws-sdk-ecs"
s.add_dependency "aws-sdk-s3"
s.add_dependency "aws-sdk-sns"
s.add_dependency "aws-sdk-sqs"
s.add_dependency "hamlit"
s.add_dependency "jquery-rails"
s.add_dependency "kaminari", ">= 1.2.1"
s.add_dependency "rails", "~> 7.0.5"
s.add_dependency "rinku"
s.add_dependency "serverengine"
s.add_dependency "sprockets-rails"
s.add_dependency "the_garage"
s.add_dependency "uglifier"
s.add_dependency "weak_parameters"
s.add_development_dependency "autodoc"
s.add_development_dependency "factory_bot_rails", ">= 5.0.0"
s.add_development_dependency "listen"
s.add_development_dependency "mysql2"
s.add_development_dependency "rspec-rails"
end
================================================
FILE: bin/rails
================================================
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails gems
# installed from the root of your application.
ENGINE_ROOT = File.expand_path('../..', __FILE__)
ENGINE_PATH = File.expand_path('../../lib/barbeque/engine', __FILE__)
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
require 'rails/all'
require 'rails/engine/commands'
================================================
FILE: compose.yaml
================================================
services:
db:
image: mysql:8.0
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: barbeque_root
MYSQL_USER: barbeque
MYSQL_PASSWORD: barbeque
MYSQL_DATABASE: barbeque
================================================
FILE: config/initializers/garage.rb
================================================
require 'garage'
Garage.configure {}
Garage.configuration.strategy = Garage::Strategy::NoAuthentication
================================================
FILE: config/routes.rb
================================================
Barbeque::Engine.routes.draw do
root to: 'apps#index'
resources :apps, except: :index
resources :job_definitions do
get :stats
get :execution_stats
end
resources :job_executions, only: :show, param: :message_id do
post :retry
resources :job_retries, only: :show
end
resources :job_queues do
member do
get :sqs_attributes
end
collection do
get :sqs_metrics
end
end
resources :sns_subscriptions
resources :monitors, only: %i[index]
scope :v1, module: 'api', as: :v1 do
resources :apps, only: [], param: :name, constraints: { name: /[\w-]+/ } do
resource :revision_lock, only: [:create, :destroy]
end
resources :job_executions, only: :show, param: :message_id,
constraints: { message_id: /[a-f\d]{8}-([a-f\d]{4}-){3}[a-f\d]{12}/ } do
resources :job_retries, only: [:create], path: 'retries'
end
end
scope :v2, module: 'api', as: :v2 do
resources :job_executions, only: :create, param: :message_id,
constraints: { message_id: /[a-f\d]{8}-([a-f\d]{4}-){3}[a-f\d]{12}/ }
end
end
================================================
FILE: db/migrate/20160217020910_create_job_queues.rb
================================================
class CreateJobQueues < ActiveRecord::Migration[5.0]
def change
create_table :job_queues, options: 'ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARSET=utf8mb4' do |t|
t.string :name, null: false
t.text :description
t.string :queue_url, null: false
t.timestamps
end
add_index :job_queues, [:name], unique: true
end
end
================================================
FILE: db/migrate/20160219010912_create_job_executions.rb
================================================
class CreateJobExecutions < ActiveRecord::Migration[5.0]
def change
create_table :job_executions, options: 'ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARSET=utf8mb4' do |t|
t.string :message_id, null: false
t.integer :status, null: false, default: 0
t.timestamps
end
add_index :job_executions, [:message_id], unique: true
end
end
================================================
FILE: db/migrate/20160223060807_create_apps.rb
================================================
class CreateApps < ActiveRecord::Migration[5.0]
def change
create_table :apps, options: 'ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARSET=utf8mb4' do |t|
t.string :name, null: false
t.string :docker_image, null: false
t.text :description
t.timestamps
end
add_index :apps, [:name], unique: true
end
end
================================================
FILE: db/migrate/20160223183348_create_job_definitions.rb
================================================
class CreateJobDefinitions < ActiveRecord::Migration[5.0]
def change
create_table :job_definitions, options: 'ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARSET=utf8mb4' do |t|
t.string :job, null: false
t.integer :app_id, null: false
t.string :command, null: false
t.text :description
t.timestamps
end
add_index :job_definitions, [:job, :app_id], unique: true
end
end
================================================
FILE: db/migrate/20160225020801_add_job_definition_id_to_job_executions.rb
================================================
class AddJobDefinitionIdToJobExecutions < ActiveRecord::Migration[5.0]
def change
add_column :job_executions, :job_definition_id, :integer
add_index :job_executions, [:job_definition_id]
end
end
================================================
FILE: db/migrate/20160412083604_add_finished_at_to_job_executions.rb
================================================
class AddFinishedAtToJobExecutions < ActiveRecord::Migration[5.0]
def change
add_column :job_executions, :finished_at, :datetime
end
end
================================================
FILE: db/migrate/20160415043427_create_slack_notifications.rb
================================================
class CreateSlackNotifications < ActiveRecord::Migration[5.0]
def change
create_table :slack_notifications, options: 'ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARSET=utf8mb4' do |t|
t.integer :job_definition_id
t.string :channel, null: false
t.boolean :notify_success, default: false, null: false
t.string :failure_notification_text
t.timestamps
end
end
end
================================================
FILE: db/migrate/20160509041452_add_job_queue_id_to_job_executions.rb
================================================
class AddJobQueueIdToJobExecutions < ActiveRecord::Migration[5.0]
def change
add_column :job_executions, :job_queue_id, :integer
end
end
================================================
FILE: db/migrate/20160516041710_create_job_retries.rb
================================================
class CreateJobRetries < ActiveRecord::Migration[5.0]
def change
create_table :job_retries, options: 'ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARSET=utf8mb4' do |t|
t.string :message_id, null: false
t.integer :job_execution_id, null: false
t.integer :status, null: false, default: 0
t.datetime :finished_at
t.timestamps
end
add_index :job_retries, [:message_id], unique: true
end
end
================================================
FILE: db/migrate/20160829023237_prefix_barbeque_to_tables.rb
================================================
class PrefixBarbequeToTables < ActiveRecord::Migration[5.0]
def change
rename_table :apps, :barbeque_apps
rename_table :job_definitions, :barbeque_job_definitions
rename_table :job_executions, :barbeque_job_executions
rename_table :job_queues, :barbeque_job_queues
rename_table :job_retries, :barbeque_job_retries
rename_table :slack_notifications, :barbeque_slack_notifications
end
end
================================================
FILE: db/migrate/20170420030157_create_barbeque_sns_subscriptions.rb
================================================
class CreateBarbequeSnsSubscriptions < ActiveRecord::Migration[5.0]
def change
create_table :barbeque_sns_subscriptions do |t|
t.string :topic_arn, null: false
t.integer :job_queue_id, null: false
t.integer :job_definition_id, null: false
t.timestamps
end
end
end
================================================
FILE: db/migrate/20170711085157_create_barbeque_docker_containers.rb
================================================
class CreateBarbequeDockerContainers < ActiveRecord::Migration[5.0]
def change
create_table :barbeque_docker_containers, options: 'ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARSET=utf8mb4' do |t|
t.string :message_id, null: false
t.string :container_id, null: false
t.timestamps
t.index ['message_id'], unique: true
end
end
end
================================================
FILE: db/migrate/20170712075449_create_barbeque_ecs_hako_tasks.rb
================================================
class CreateBarbequeEcsHakoTasks < ActiveRecord::Migration[5.0]
def change
create_table :barbeque_ecs_hako_tasks, options: 'ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARSET=utf8mb4' do |t|
t.string :message_id, null: false
t.string :cluster, null: false
t.string :task_arn, null: false
t.timestamps
t.index ['message_id'], unique: true
end
end
end
================================================
FILE: db/migrate/20170724025542_add_index_to_job_execution_status.rb
================================================
class AddIndexToJobExecutionStatus < ActiveRecord::Migration[5.0]
def change
add_index :barbeque_job_executions, :status
add_index :barbeque_job_retries, :status
end
end
================================================
FILE: db/migrate/20180411070937_add_index_to_barbeque_job_executions_created_at.rb
================================================
class AddIndexToBarbequeJobExecutionsCreatedAt < ActiveRecord::Migration[5.1]
def change
add_index :barbeque_job_executions, :created_at
end
end
================================================
FILE: db/migrate/20190221050714_create_barbeque_retry_configs.rb
================================================
class CreateBarbequeRetryConfigs < ActiveRecord::Migration[5.2]
def change
create_table :barbeque_retry_configs, options: 'ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARSET=utf8mb4' do |t|
t.integer :job_definition_id, null: false
t.integer :retry_limit, null: false, default: 3
t.float :base_delay, null: false, default: '0.3'
t.integer :max_delay
t.boolean :jitter, null: false, default: true
t.timestamps
t.index [:job_definition_id], unique: true
end
end
end
================================================
FILE: db/migrate/20190311034445_add_notify_failure_only_if_retry_limit_reached_to_barbeque_slack_notifications.rb
================================================
class AddNotifyFailureOnlyIfRetryLimitReachedToBarbequeSlackNotifications < ActiveRecord::Migration[5.2]
def change
add_column :barbeque_slack_notifications, :notify_failure_only_if_retry_limit_reached, :boolean, default: false, null: false
end
end
================================================
FILE: db/migrate/20190315052951_change_barbeque_retry_configs_base_delay_default_value.rb
================================================
class ChangeBarbequeRetryConfigsBaseDelayDefaultValue < ActiveRecord::Migration[5.2]
def up
change_column_default :barbeque_retry_configs, :base_delay, 15
end
def down
change_column_default :barbeque_retry_configs, :base_delay, 0.3
end
end
================================================
FILE: db/migrate/20191029105530_change_job_name_to_case_sensitive.rb
================================================
class ChangeJobNameToCaseSensitive < ActiveRecord::Migration[5.2]
def up
change_column :barbeque_job_definitions, :job, :string, collation: 'utf8mb4_bin'
end
def down
change_column :barbeque_job_definitions, :job, :string, collation: nil
end
end
================================================
FILE: db/migrate/20240415080757_fix_collations.rb
================================================
# In MySQL 8.0, the default collation for utf8mb4 is changed to utf8mb4_0900_ai_ci.
# The column collations are utf8mb4_0900_ai_ci after we ran migrations prior to this
# on MySQL 8.0 because we did not specify collation in create_table (thus the default is used).
# This happens on testing/development but we want to keep utf8mb4_general_ci.
#
# Note that running this should not affect the actual schema and data
# unless you've ran prior migrations on MySQL 8.0.
class FixCollations < ActiveRecord::Migration[6.1]
def change
reversible do |direction|
direction.up do
# alter column collations and default collation of every tables defined to utf8mb4_general_ci
execute 'ALTER TABLE barbeque_apps CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
execute 'ALTER TABLE barbeque_docker_containers CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
execute 'ALTER TABLE barbeque_ecs_hako_tasks CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
execute 'ALTER TABLE barbeque_job_executions CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
execute 'ALTER TABLE barbeque_job_queues CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
execute 'ALTER TABLE barbeque_job_retries CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
execute 'ALTER TABLE barbeque_retry_configs CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
execute 'ALTER TABLE barbeque_slack_notifications CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
execute 'ALTER TABLE barbeque_sns_subscriptions CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
# barbeque_job_definitions contains a column with explicitly specified collation
execute 'ALTER TABLE barbeque_job_definitions DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci'
change_column :barbeque_job_definitions, :command, :string, collation: 'utf8mb4_general_ci'
change_column :barbeque_job_definitions, :description, :text, collation: 'utf8mb4_general_ci'
end
direction.down do
raise ActiveRecord::IrreversibleMigration
end
end
end
end
================================================
FILE: doc/api/job_executions.md
================================================
## GET /v1/job_executions/:message_id
Shows a status of a job_execution.
### Example
#### Request
```
GET /v1/job_executions/147e070d-41a9-4ace-a122-2aa579e18f08 HTTP/1.1
Accept: application/json
Content-Length: 0
Content-Type: application/json
Host: www.example.com
```
#### Response
```
HTTP/1.1 200
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 81
Content-Type: application/json; charset=utf-8
ETag: W/"7e91b6b9d378eb1b93717767ccdccc68"
X-Request-Id: 19b16855-477c-4f8a-99a6-db203a472bfe
X-Runtime: 0.008698
{
"message_id": "147e070d-41a9-4ace-a122-2aa579e18f08",
"status": "success",
"id": 257
}
```
## GET /v1/job_executions/:message_id
Shows url to job_execution.
### Example
#### Request
```
GET /v1/job_executions/060a38f2-97a8-42ea-8250-b0c66b8dad77?fields=__default__,html_url HTTP/1.1
Accept: application/json
Content-Length: 0
Content-Type: application/json
Host: www.example.com
```
#### Response
```
HTTP/1.1 200
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 163
Content-Type: application/json; charset=utf-8
ETag: W/"dfeb94a1453a12315b86db92ddf3fe01"
X-Request-Id: 8a7713b2-305f-4daf-b877-dc288147915c
X-Runtime: 0.002219
{
"message_id": "060a38f2-97a8-42ea-8250-b0c66b8dad77",
"status": "success",
"id": 258,
"html_url": "https://barbeque/job_executions/060a38f2-97a8-42ea-8250-b0c66b8dad77"
}
```
## GET /v1/job_executions/:message_id
Returns message of the job_execution.
### Example
#### Request
```
GET /v1/job_executions/d3052505-355f-44aa-9565-9ac325960f6b?fields=__default__,message HTTP/1.1
Accept: application/json
Content-Length: 0
Content-Type: application/json
Host: www.example.com
```
#### Response
```
HTTP/1.1 200
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 111
Content-Type: application/json; charset=utf-8
ETag: W/"06553c660e7287f2b1e5d29f01b494f7"
X-Request-Id: e8c5596a-ec4a-407c-9a8d-864d23a32044
X-Runtime: 0.001963
{
"message_id": "d3052505-355f-44aa-9565-9ac325960f6b",
"status": "success",
"id": 259,
"message": {
"recipe_id": 12345
}
}
```
## GET /v1/job_executions/:message_id
Returns error message.
### Example
#### Request
```
GET /v1/job_executions/4df2cbf1-bfb6-46f8-9412-e6adc2349bbe HTTP/1.1
Accept: application/json
Content-Length: 0
Content-Type: application/json
Host: www.example.com
```
#### Response
```
HTTP/1.1 503
Cache-Control: no-cache
Content-Length: 237
Content-Type: application/json; charset=utf-8
X-Request-Id: 78d64fec-e4b5-4a93-8a00-e7201ba7b3ce
X-Runtime: 0.002017
{
"message": "Mysql2::Error::ConnectionError: Can't connect to MySQL server: SELECT `barbeque_job_executions`.* FROM `barbeque_job_executions` WHERE `barbeque_job_executions`.`message_id` = '4df2cbf1-bfb6-46f8-9412-e6adc2349bbe' LIMIT 1"
}
```
## POST /v2/job_executions
Enqueues a job execution.
### Parameters
* `application` string (required) - Application name of the job
* `job` string (required) - Class of Job to be enqueued
* `queue` string (required) - Queue name to enqueue a job
* `message` any (required) - Free-format JSON
* `delay_seconds` integer - Set message timer of SQS
### Example
#### Request
```
POST /v2/job_executions HTTP/1.1
Accept: application/json
Content-Length: 89
Content-Type: application/json
Host: www.example.com
{
"application": "blog",
"job": "NotifyAuthor",
"queue": "queue-104",
"message": {
"recipe_id": 1
}
}
```
#### Response
```
HTTP/1.1 201
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 82
Content-Type: application/json; charset=utf-8
ETag: W/"906cbe627aa59752a3654cdc059b930a"
X-Request-Id: ece6706b-cd9b-4d24-9093-7d5bb248bd35
X-Runtime: 0.001741
{
"message_id": "6dc3cbba-77fe-4922-bd9f-273def490f72",
"status": "pending",
"id": null
}
```
## POST /v2/job_executions
Enqueues a job execution with delay_seconds.
### Parameters
* `application` string (required) - Application name of the job
* `job` string (required) - Class of Job to be enqueued
* `queue` string (required) - Queue name to enqueue a job
* `message` any (required) - Free-format JSON
* `delay_seconds` integer - Set message timer of SQS
### Example
#### Request
```
POST /v2/job_executions HTTP/1.1
Accept: application/json
Content-Length: 109
Content-Type: application/json
Host: www.example.com
{
"application": "blog",
"job": "NotifyAuthor",
"queue": "queue-107",
"message": {
"recipe_id": 1
},
"delay_seconds": 300
}
```
#### Response
```
HTTP/1.1 201
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 82
Content-Type: application/json; charset=utf-8
ETag: W/"2584b3edd06144ea19b89616b028d0e0"
X-Request-Id: 1f9c99b9-8540-4897-9bfc-fc1f6dec2260
X-Runtime: 0.005125
{
"message_id": "b1413290-1b07-4e0d-b3df-817204c14daa",
"status": "pending",
"id": null
}
```
================================================
FILE: doc/api/job_retries.md
================================================
## POST /v1/job_executions/:job_execution_message_id/retries
Enqueues a message to retry a specified message.
### Parameters
* `delay_seconds` integer (only: `0..900`)
### Example
#### Request
```
POST /v1/job_executions/8b861a99-a45b-4720-851a-805c1bc70287/retries HTTP/1.1
Accept: application/json
Content-Length: 0
Content-Type: application/json
Host: www.example.com
```
#### Response
```
HTTP/1.1 201
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 72
Content-Type: application/json; charset=utf-8
ETag: W/"e03d4f08be4c8b4dfd18839dd48708e8"
X-Request-Id: 9c2a3b6f-3888-47da-bc2a-d75137af73bc
X-Runtime: 0.004713
{
"message_id": "af18541e-9668-4eb2-ac5b-1899dbbe7e1b",
"status": "pending"
}
```
================================================
FILE: doc/api/revision_locks.md
================================================
## POST /v1/apps/:app_id/revision_lock
Updates a tag of docker_image.
### Parameters
* `revision` string (required) - Docker image revision to lock
### Example
#### Request
```
POST /v1/apps/app-107/revision_lock HTTP/1.1
Accept: application/json
Content-Length: 55
Content-Type: application/json
Host: www.example.com
{
"revision": "798926db1e623cd51245b70b1f1acb40d780ddc1"
}
```
#### Response
```
HTTP/1.1 201
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 55
Content-Type: application/json; charset=utf-8
ETag: W/"a3f0b3d8c32ee1e318357b5276e50a3c"
X-Request-Id: 0a4f9e4b-d80e-4788-ad77-666152308941
X-Runtime: 0.008506
{
"revision": "798926db1e623cd51245b70b1f1acb40d780ddc1"
}
```
## DELETE /v1/apps/:app_id/revision_lock
Updates a tag of docker_image.
### Example
#### Request
```
DELETE /v1/apps/app-109/revision_lock HTTP/1.1
Accept: application/json
Content-Length: 0
Content-Type: application/json
Host: www.example.com
```
#### Response
```
HTTP/1.1 204
Cache-Control: no-cache
X-Request-Id: 29434e92-0539-40cc-b38d-011e4d0c6347
X-Runtime: 0.006990
```
================================================
FILE: doc/toc.md
================================================
## Table of Contents
* [api/job_executions.md](api/job_executions.md)
* [GET /v1/job_executions/:message_id](api/job_executions.md#get-v1job_executionsmessage_id)
* [GET /v1/job_executions/:message_id](api/job_executions.md#get-v1job_executionsmessage_id-1)
* [GET /v1/job_executions/:message_id](api/job_executions.md#get-v1job_executionsmessage_id-2)
* [GET /v1/job_executions/:message_id](api/job_executions.md#get-v1job_executionsmessage_id-3)
* [POST /v2/job_executions](api/job_executions.md#post-v2job_executions)
* [POST /v2/job_executions](api/job_executions.md#post-v2job_executions-1)
* [api/job_retries.md](api/job_retries.md)
* [POST /v1/job_executions/:job_execution_message_id/retries](api/job_retries.md#post-v1job_executionsjob_execution_message_idretries)
* [api/revision_locks.md](api/revision_locks.md)
* [POST /v1/apps/:app_id/revision_lock](api/revision_locks.md#post-v1appsapp_idrevision_lock)
* [DELETE /v1/apps/:app_id/revision_lock](api/revision_locks.md#delete-v1appsapp_idrevision_lock)
================================================
FILE: lib/barbeque/config.rb
================================================
require 'erb'
require 'yaml'
module Barbeque
class Config
attr_accessor :exception_handler, :executor, :executor_options, :sqs_receive_message_wait_time, :maximum_concurrent_executions, :runner_wait_seconds
def initialize(options = {})
options.each do |key, value|
if respond_to?("#{key}=")
public_send("#{key}=", value)
else
raise KeyError.new("Unexpected option '#{key}' was specified.")
end
end
executor_options.symbolize_keys!
end
end
module ConfigBuilder
DEFAULT_CONFIG = {
'exception_handler' => 'RailsLogger',
'executor' => 'Docker',
'executor_options' => {},
# http://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_CreateQueue.html#API_CreateQueue_RequestParameters
'sqs_receive_message_wait_time' => 10,
# nil means unlimited
'maximum_concurrent_executions' => nil,
'runner_wait_seconds' => 10,
}
def config
@config ||= build_config
end
def build_config(config_name = 'barbeque')
Config.new(DEFAULT_CONFIG.merge(Rails.application.config_for(config_name)))
end
end
extend ConfigBuilder
end
================================================
FILE: lib/barbeque/docker_image.rb
================================================
module Barbeque
class DockerImage
DEFAULT_TAG = 'latest'
def initialize(str)
# See: https://github.com/docker/docker/blob/v1.10.2/image/spec/v1.md
result = str.match(%r{((?<registry>[^/]+)?/)?(?<repository>[\w./-]+)(:(?<tag>[\w.-]+))?\z})
@repository = result[:repository]
@tag = result[:tag] || DEFAULT_TAG
@registry = result[:registry] || ENV['BARBEQUE_DOCKER_REGISTRY']
end
attr_reader :registry, :repository
attr_accessor :tag
def to_s
[registry, "#{repository}:#{tag}"].compact.join('/')
end
end
end
================================================
FILE: lib/barbeque/engine.rb
================================================
# Gems used by Barbeque::Engine
require 'rinku'
module Barbeque
class Engine < ::Rails::Engine
isolate_namespace Barbeque
config.before_configuration do
# Gems used by Barbeque::Engine, which also have Railtie or Mountable::Engine.
# Railtie and Mountable::Engine aren't executed when required normally.
require 'hamlit'
require 'jquery-rails'
require 'kaminari'
require 'sprockets/railtie'
require 'weak_parameters'
end
end
end
================================================
FILE: lib/barbeque/exception_handler.rb
================================================
require 'barbeque/config'
module Barbeque
module ExceptionHandler
class << self
delegate :clear_context, :set_message_context, :handle_exception, to: :handler
private
def handler
@handler ||= const_get(Barbeque.config.exception_handler, false).new
end
end
class RailsLogger
def initialize
clear_context
end
def clear_context
@message_id = nil
@message_type = nil
end
# @param [String] message_id
# @param [String, nil] message_type
def set_message_context(message_id, message_type)
@message_id = message_id
@message_type = message_type
end
# @param [Exception] e
def handle_exception(e)
Rails.logger.error("#{e.inspect}\nmessage_id: #{@message_id}, message_type: #{@message_type}\n#{e.backtrace.join("\n")}")
end
end
class Raven
def clear_context
::Raven::Context.clear!
end
# @param [String] message_id
# @param [String, nil] message_type
def set_message_context(message_id, message_type)
::Raven.tags_context(message_id: message_id, message_type: message_type)
end
# @param [Exception] e
def handle_exception(e)
::Raven.capture_exception(e)
end
end
class Sentry
def initialize
::Sentry.get_current_hub.push_scope
end
def clear_context
::Sentry.get_current_hub.pop_scope
::Sentry.get_current_hub.push_scope
end
# @param [String] message_id
# @param [String, nil] message_type
def set_message_context(message_id, message_type)
::Sentry.configure_scope do |scope|
scope.set_tags(
message_id: message_id,
message_type: message_type
)
end
end
# @param [Exception] e
def handle_exception(e)
::Sentry.capture_exception(e)
end
end
end
end
================================================
FILE: lib/barbeque/execution_log.rb
================================================
require 'aws-sdk-s3'
require 'active_support'
require 'active_support/core_ext'
require 'barbeque/exception_handler'
module Barbeque
class ExecutionLog
DEFAULT_S3_BUCKET_NAME = 'barbeque'
class << self
delegate :save_message, :save_stdout_and_stderr, :try_save_stdout_and_stderr, :load, to: :new
def s3_client
@s3_client ||= Aws::S3::Client.new
end
end
# @param [Barbeque::JobExecution] execution
# @param [Barbeque::Message::JobExecution] message
def save_message(execution, message)
put(execution, 'message.json', message.body.to_json)
end
# @param [Barbeque::JobExecution,Barbeque::JobRetry] execution
# @param [String] stdout
# @param [String] stderr
def save_stdout_and_stderr(execution, stdout, stderr)
put(execution, 'stdout.txt', stdout)
put(execution, 'stderr.txt', stderr)
end
# @param [Barbeque::JobExecution,Barbeque::JobRetry] execution
# @param [String] stdout
# @param [String] stderr
def try_save_stdout_and_stderr(execution, stdout, stderr)
save_stdout_and_stderr(execution, stdout, stderr)
rescue Aws::S3::Errors::ServiceError => e
ExceptionHandler.handle_exception(e)
end
# @param [Barbeque::JobExecution,Barbeque::JobRetry] execution
# @return [Hash] log
def load(execution:)
return {} if execution.pending?
message = get(execution, 'message.json')
stdout = get(execution, 'stdout.txt')
stderr = get(execution, 'stderr.txt')
if message || stdout || stderr
{
'message' => message,
'stdout' => stdout,
'stderr' => stderr,
}
else
nil
end
end
private
def s3_bucket_name
@s3_bucket_name ||= ENV['BARBEQUE_S3_BUCKET_NAME'] || DEFAULT_S3_BUCKET_NAME
end
# @param [Barbeque::JobExecution,Barbeque::JobRetry] execution
# @param [String] filename
def s3_key_for(execution, filename)
"#{execution.app.name}/#{execution.job_definition.job}/#{execution.message_id}/#{filename}"
end
# @param [Barbeque::JobExecution,Barbeque::JobRetry] execution
# @param [String] filename
# @return [String]
def get(execution, filename)
s3_object = ExecutionLog.s3_client.get_object(
bucket: s3_bucket_name,
key: s3_key_for(execution, filename),
)
s3_object.body.read
rescue Aws::S3::Errors::NoSuchKey
nil
end
# @param [Barbeque::JobExecution,Barbeque::JobRetry] execution
# @param [String] filename
# @param [String] content
def put(execution, filename, content)
ExecutionLog.s3_client.put_object(
bucket: s3_bucket_name,
key: s3_key_for(execution, filename),
body: content,
)
end
end
end
================================================
FILE: lib/barbeque/execution_poller.rb
================================================
require 'barbeque/exception_handler'
require 'barbeque/executor'
module Barbeque
class ExecutionPoller
def initialize(job_queue)
@job_queue = job_queue
@stop_requested = false
end
def run
@job_queue.job_executions.running.find_in_batches do |job_executions|
job_executions.shuffle.each do |job_execution|
if @stop_requested
return
end
job_execution.with_lock do
if job_execution.running?
poll(job_execution)
end
end
end
end
sleep 1
end
def stop
@stop_requested = true
end
private
def poll(job_execution)
Barbeque::ExceptionHandler.set_message_context(job_execution.message_id, nil)
executor = Executor.create
executor.poll_execution(job_execution)
end
end
end
================================================
FILE: lib/barbeque/executor/docker.rb
================================================
require 'barbeque/docker_image'
require 'barbeque/execution_log'
require 'barbeque/slack_notifier'
require 'open3'
module Barbeque
module Executor
class Docker
class DockerCommandError < StandardError
end
def initialize(**)
end
# @param [Barbeque::JobExecution] job_execution
# @param [Hash] envs
def start_execution(job_execution, envs)
docker_image = DockerImage.new(job_execution.job_definition.app.docker_image)
cmd = build_docker_run_command(docker_image, job_execution.job_definition.command, envs)
stdout, stderr, status = Open3.capture3(*cmd)
if status.success?
Barbeque::DockerContainer.create!(message_id: job_execution.message_id, container_id: stdout.chomp)
job_execution.update!(status: :running)
else
Barbeque::ExecutionLog.try_save_stdout_and_stderr(job_execution, stdout, stderr)
job_execution.update!(status: :failed, finished_at: Time.zone.now)
Barbeque::SlackNotifier.notify_job_execution(job_execution)
job_execution.retry_if_possible!
end
end
# @param [Barbeque::JobRetry] job_retry
# @param [Hash] envs
def start_retry(job_retry, envs)
job_execution = job_retry.job_execution
docker_image = DockerImage.new(job_execution.job_definition.app.docker_image)
cmd = build_docker_run_command(docker_image, job_execution.job_definition.command, envs)
stdout, stderr, status = Open3.capture3(*cmd)
if status.success?
Barbeque::DockerContainer.create!(message_id: job_retry.message_id, container_id: stdout.chomp)
Barbeque::ApplicationRecord.transaction do
job_execution.update!(status: :retried)
job_retry.update!(status: :running)
end
else
Barbeque::ExecutionLog.try_save_stdout_and_stderr(job_retry, stdout, stderr)
Barbeque::ApplicationRecord.transaction do
job_retry.update!(status: :failed, finished_at: Time.zone.now)
job_execution.update!(status: :failed)
end
Barbeque::SlackNotifier.notify_job_retry(job_retry)
job_execution.retry_if_possible!
end
end
# @param [Barbeque::JobExecution] job_execution
def poll_execution(job_execution)
container = Barbeque::DockerContainer.find_by!(message_id: job_execution.message_id)
info = inspect_container(container.container_id)
if info['State'] && info['State']['Status'] != 'running'
finished_at = Time.zone.parse(info['State']['FinishedAt'])
exit_code = info['State']['ExitCode']
job_execution.update!(status: exit_code == 0 ? :success : :failed, finished_at: finished_at)
stdout, stderr = get_logs(container.container_id)
Barbeque::ExecutionLog.save_stdout_and_stderr(job_execution, stdout, stderr)
Barbeque::SlackNotifier.notify_job_execution(job_execution)
if exit_code != 0
job_execution.retry_if_possible!
end
end
end
# @param [Barbeque::JobRetry] job_retry
def poll_retry(job_retry)
container = Barbeque::DockerContainer.find_by!(message_id: job_retry.message_id)
job_execution = job_retry.job_execution
info = inspect_container(container.container_id)
if info['State'] && info['State']['Status'] != 'running'
finished_at = Time.zone.parse(info['State']['FinishedAt'])
exit_code = info['State']['ExitCode']
status = exit_code == 0 ? :success : :failed
Barbeque::ApplicationRecord.transaction do
job_retry.update!(status: status, finished_at: finished_at)
job_execution.update!(status: status)
end
stdout, stderr = get_logs(container.container_id)
Barbeque::ExecutionLog.save_stdout_and_stderr(job_retry, stdout, stderr)
Barbeque::SlackNotifier.notify_job_retry(job_retry)
if status == :failed
job_execution.retry_if_possible!
end
end
end
private
# @param [Barbeque::DockerImage] docker_image
# @param [Array<String>] command
# @param [Hash] envs
def build_docker_run_command(docker_image, command, envs)
['docker', 'run', '--detach', *env_options(envs), docker_image.to_s, *command]
end
def env_options(envs)
envs.flat_map do |key, value|
['--env', "#{key}=#{value}"]
end
end
# @param [String] container_id
# @return [Hash] container info
def inspect_container(container_id)
stdout, stderr, status = Open3.capture3('docker', 'inspect', container_id)
if status.success?
begin
JSON.parse(stdout)[0]
rescue JSON::ParserError => e
raise DockerCommandError.new("Unable to parse JSON: #{e.class}: #{e.message}: #{stdout}")
end
else
raise DockerCommandError.new("Unable to inspect Docker container #{container.container_id}: STDOUT: #{stdout}; STDERR: #{stderr}")
end
end
# @param [String] container_id
# @return [String] stdout
# @return [String] stderr
def get_logs(container_id)
stdout, stderr, status = Open3.capture3('docker', 'logs', container_id)
if status.success?
[stdout, stderr]
else
raise DockerCommandError.new("Unable to get Docker container logs #{container.container_id}: STDOUT: #{stdout}; STDERR: #{stderr}")
end
end
end
end
end
================================================
FILE: lib/barbeque/executor/hako.rb
================================================
require 'barbeque/docker_image'
require 'barbeque/execution_log'
require 'barbeque/hako_s3_client'
require 'barbeque/slack_notifier'
require 'open3'
module Barbeque
module Executor
class Hako
class HakoCommandError < StandardError
end
attr_reader :hako_s3_client
# @param [String] hako_dir
# @param [Hash] hako_env
# @param [String] definition_dir
# @param [String] yaml_dir (deprecated: renamed to definition_dir)
def initialize(hako_dir:, hako_env: {}, yaml_dir: nil, definition_dir: nil, oneshot_notification_prefix:)
@hako_dir = hako_dir
@hako_env = hako_env.stringify_keys
@definition_dir =
if definition_dir
definition_dir
elsif yaml_dir
warn 'yaml_dir option is renamed to definition_dir. Please update config/barbeque.yml'
yaml_dir
else
raise ArgumentError.new('definition_dir is required')
end
@hako_s3_client = HakoS3Client.new(oneshot_notification_prefix)
end
# @param [Barbeque::JobExecution] job_execution
# @param [Hash] envs
def start_execution(job_execution, envs)
docker_image = DockerImage.new(job_execution.job_definition.app.docker_image)
cmd = build_hako_oneshot_command(docker_image, job_execution.job_definition.command, envs)
stdout, stderr, status = Bundler.with_unbundled_env { Open3.capture3(@hako_env, *cmd, chdir: @hako_dir) }
if status.success?
cluster, task_arn = extract_task_info(stdout)
Barbeque::EcsHakoTask.create!(message_id: job_execution.message_id, cluster: cluster, task_arn: task_arn)
Barbeque::ExecutionLog.try_save_stdout_and_stderr(job_execution, stdout, stderr)
job_execution.update!(status: :running)
else
Barbeque::ExecutionLog.try_save_stdout_and_stderr(job_execution, stdout, stderr)
job_execution.update!(status: :failed, finished_at: Time.zone.now)
Barbeque::SlackNotifier.notify_job_execution(job_execution)
job_execution.retry_if_possible!
end
end
# @param [Barbeque::JobRetry] job_retry
# @param [Hash] envs
def start_retry(job_retry, envs)
job_execution = job_retry.job_execution
docker_image = DockerImage.new(job_execution.job_definition.app.docker_image)
cmd = build_hako_oneshot_command(docker_image, job_execution.job_definition.command, envs)
stdout, stderr, status = Bundler.with_unbundled_env { Open3.capture3(@hako_env, *cmd, chdir: @hako_dir) }
if status.success?
cluster, task_arn = extract_task_info(stdout)
Barbeque::EcsHakoTask.create!(message_id: job_retry.message_id, cluster: cluster, task_arn: task_arn)
Barbeque::ExecutionLog.try_save_stdout_and_stderr(job_retry, stdout, stderr)
Barbeque::ApplicationRecord.transaction do
job_execution.update!(status: :retried)
job_retry.update!(status: :running)
end
else
Barbeque::ExecutionLog.try_save_stdout_and_stderr(job_retry, stdout, stderr)
Barbeque::ApplicationRecord.transaction do
job_retry.update!(status: :failed, finished_at: Time.zone.now)
job_execution.update!(status: :failed)
end
Barbeque::SlackNotifier.notify_job_retry(job_retry)
job_execution.retry_if_possible!
end
end
# @param [Barbeque::JobExecution] job_execution
def poll_execution(job_execution)
hako_task = Barbeque::EcsHakoTask.find_by!(message_id: job_execution.message_id)
task = @hako_s3_client.get_stopped_result(hako_task)
if task
status = :failed
task.containers.each do |container|
if container.name == 'app'
status = container.exit_code == 0 ? :success : :failed
end
end
job_execution.update!(status: status, finished_at: task.stopped_at)
Barbeque::SlackNotifier.notify_job_execution(job_execution)
if status == :failed
job_execution.retry_if_possible!
end
end
end
# @param [Barbeque::JobRetry] job_execution
def poll_retry(job_retry)
hako_task = Barbeque::EcsHakoTask.find_by!(message_id: job_retry.message_id)
job_execution = job_retry.job_execution
task = @hako_s3_client.get_stopped_result(hako_task)
if task
status = :failed
task.containers.each do |container|
if container.name == 'app'
status = container.exit_code == 0 ? :success : :failed
end
end
Barbeque::ApplicationRecord.transaction do
job_retry.update!(status: status, finished_at: task.stopped_at)
job_execution.update!(status: status)
end
Barbeque::SlackNotifier.notify_job_retry(job_retry)
if status == :failed
job_execution.retry_if_possible!
end
end
end
private
def build_hako_oneshot_command(docker_image, command, envs)
jsonnet_path = File.join(@definition_dir, "#{docker_image.repository}.jsonnet")
yaml_path = File.join(@definition_dir, "#{docker_image.repository}.yml")
cmd = ['bundle', 'exec', 'hako', 'oneshot', '--no-wait', '--tag', docker_image.tag, *env_options(envs)]
if File.readable?(jsonnet_path)
cmd << jsonnet_path
elsif File.readable?(yaml_path)
cmd << yaml_path
else
raise HakoCommandError.new("No definition found matching '#{docker_image.repository}' in #{@definition_dir}")
end
cmd << '--'
cmd.concat(command)
end
def env_options(envs)
envs.map do |key, value|
"--env=#{key}=#{value}"
end
end
def extract_task_info(stdout)
last_line = stdout.lines.last
if last_line
begin
task_info = JSON.parse(last_line)
cluster = task_info['cluster']
task_arn = task_info['task_arn']
if cluster && task_arn
[cluster, task_arn]
else
raise HakoCommandError.new("Unable find cluster and task_arn in JSON: #{stdout}")
end
rescue JSON::ParserError => e
raise HakoCommandError.new("Unable parse the last line as JSON: #{stdout}")
end
else
raise HakoCommandError.new('stdout is empty')
end
end
end
end
end
================================================
FILE: lib/barbeque/executor.rb
================================================
require 'barbeque/config'
require 'barbeque/executor/docker'
require 'barbeque/executor/hako'
module Barbeque
# Executor is responsible for starting executions and getting status of
# executions.
# Each executor must implement these methods.
# - #initialize(options)
# - Create a executor with executor_options specified in config/barbeque.yml.
# - #start_execution(job_execution, envs)
# - Start execution with environment variables. An executor must update the
# execution status from pending.
# - #poll_execution(job_execution)
# - Get the execution status and update the job_execution columns.
# - #start_retry(job_retry, envs)
# - Start retry with environment variables. An executor must update the
# retry status from pending and the corresponding execution status.
# - #poll_retry(job_retry)
# - Get the execution status and update the job_retry and job_execution
# columns.
module Executor
def self.create
klass = const_get(Barbeque.config.executor, false)
klass.new(**Barbeque.config.executor_options)
end
end
end
================================================
FILE: lib/barbeque/hako_s3_client.rb
================================================
require 'uri'
require 'aws-sdk-ecs'
require 'aws-sdk-s3'
module Barbeque
class HakoS3Client
# @param [String] oneshot_notification_prefix S3 location for oneshot notification
def initialize(oneshot_notification_prefix)
uri = URI.parse(oneshot_notification_prefix)
@s3_bucket = uri.host
@s3_prefix = uri.path.sub(%r{\A/}, '')
@s3_region = URI.decode_www_form(uri.query || '').to_h['region']
end
# @param [Barbeque::EcsHakoTask] hako_task
# @return [String]
def s3_key_for_stopped_result(hako_task)
"#{@s3_prefix}/#{hako_task.task_arn}/stopped.json"
end
# @return [Aws::S3::Client]
def s3_client
@s3_client ||= Aws::S3::Client.new(region: @s3_region, http_read_timeout: 5)
end
# @param [Barbeque::EcsHakoTask] hako_task
# @return [Aws::ECS::Types::Task, nil]
def get_stopped_result(hako_task)
object = s3_client.get_object(bucket: @s3_bucket, key: s3_key_for_stopped_result(hako_task))
result = JSON.parse(object.body.read)
detail = result.fetch('detail')
Aws::Json::Parser.new(Aws::ECS::Client.api.operation('describe_tasks').output.shape.member(:tasks).shape.member).parse(JSON.dump(detail))
rescue Aws::S3::Errors::NoSuchKey
nil
end
end
end
================================================
FILE: lib/barbeque/maintenance.rb
================================================
module Barbeque
module Maintenance
def self.database_maintenance_mode?
ENV['BARBEQUE_DATABASE_MAINTENANCE'] == '1' && ENV['AWS_REGION'].present? && ENV['AWS_ACCOUNT_ID'].present?
end
end
end
================================================
FILE: lib/barbeque/message/base.rb
================================================
module Barbeque
module Message
# A model wrapping Aws::SQS::Types::Message.
class Base
attr_reader :id # [String] Barbeque::JobExecution is associated via `message_id`
attr_reader :receipt_handle # [String] Used to ack a message
attr_reader :type # [String] "JobExecution", "JobRetry", etc
attr_reader :sent_timestamp # [String] The time the message was sent to the queue (epoch time in milliseconds)
# @param [Aws::SQS::Types::Message] raw_message
# @param message_body [Hash] parse result of `raw_message.body`
def initialize(raw_message, message_body)
assign_body(message_body)
@id = raw_message.message_id
@receipt_handle = raw_message.receipt_handle
@sent_timestamp = raw_message.attributes['SentTimestamp']
end
# To distinguish with `Barbeque::Message::InvalidMessage`
def valid?
true
end
private
def assign_body(message_body)
@type = message_body['Type']
end
end
end
end
================================================
FILE: lib/barbeque/message/invalid_message.rb
================================================
require 'barbeque/message/base'
module Barbeque
module Message
class InvalidMessage < Base
def valid?
false
end
end
end
end
================================================
FILE: lib/barbeque/message/job_execution.rb
================================================
require 'barbeque/message/base'
module Barbeque
module Message
class JobExecution < Base
attr_reader :body # [Object] free-format JSON
attr_reader :application # [String] To specify `job_definitions.app.name`
attr_reader :job # [String] To specify `job_definitions.job`
private
def assign_body(message_body)
super
@application = message_body['Application']
@job = message_body['Job']
@body = message_body['Message']
end
end
end
end
================================================
FILE: lib/barbeque/message/job_retry.rb
================================================
require 'barbeque/message/base'
module Barbeque
module Message
class JobRetry < Base
attr_reader :retry_message_id # [String] JobExection's message_id
private
def assign_body(message_body)
super
@retry_message_id = message_body['RetryMessageId']
end
end
end
end
================================================
FILE: lib/barbeque/message/notification.rb
================================================
require 'barbeque/message/base'
module Barbeque
module Message
class Notification < Base
attr_reader :body # [Object] free-format JSON
attr_reader :topic_arn # [String] To specify `subscription.topic_arn`
attr_reader :application # [String] To specify `job_definitions.app.name`
attr_reader :job # [String] To specify `job_definitions.job`
# @param [Barneque::SnsSubscription] subscription
# @return [Barbeque::Message::Notification]
def set_params_from_subscription(subscription)
@application = subscription.app.name
@job = subscription.job_definition.job
self
end
private
def assign_body(message_body)
super
@topic_arn = message_body['TopicArn']
@body = JSON.parse(message_body['Message'])
end
end
end
end
================================================
FILE: lib/barbeque/message.rb
================================================
require 'barbeque/exception_handler'
require 'barbeque/message/base'
require 'barbeque/message/invalid_message'
require 'barbeque/message/job_execution'
require 'barbeque/message/job_retry'
require 'barbeque/message/notification'
module Barbeque
module Message
class << self
# @param [Aws::SQS::Types::Message] raw_message
# @return [Barbeque::Message::Base]
def parse(raw_message)
body = JSON.parse(raw_message.body)
klass = find_class(body['Type'])
klass.new(raw_message, body)
rescue JSON::ParserError => e
ExceptionHandler.handle_exception(e)
InvalidMessage.new(raw_message, {})
end
private
def find_class(type)
Message.const_get(type, false)
rescue NameError => e
ExceptionHandler.handle_exception(e)
InvalidMessage
end
end
end
end
================================================
FILE: lib/barbeque/message_handler/job_execution.rb
================================================
require 'barbeque/execution_log'
require 'barbeque/executor'
require 'barbeque/slack_notifier'
module Barbeque
module MessageHandler
class JobExecution
# @param [Barbeque::Message::JobExecution] message
# @param [Barbeque::MessageQueue] message_queue
def initialize(message:, message_queue:)
@message = message
@message_queue = message_queue
end
def run
job_execution = create_job_execution
begin
Executor.create.start_execution(job_execution, job_envs)
rescue Exception => e
job_execution.update!(status: :error, finished_at: Time.now)
Barbeque::ExecutionLog.save_stdout_and_stderr(job_execution, '', "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
Barbeque::SlackNotifier.notify_job_execution(job_execution)
raise e
end
end
private
def job_envs
{
'BARBEQUE_JOB' => @message.job,
'BARBEQUE_MESSAGE' => @message.body.to_json,
'BARBEQUE_MESSAGE_ID' => @message.id,
'BARBEQUE_QUEUE_NAME' => @message_queue.job_queue.name,
'BARBEQUE_RETRY_COUNT' => '0',
'BARBEQUE_SENT_TIMESTAMP' => @message.sent_timestamp,
}
end
def job_definition
@job_definition ||= Barbeque::JobDefinition.joins(:app).find_by!(
job: @message.job,
barbeque_apps: { name: @message.application },
)
end
def create_job_execution
Barbeque::JobExecution.transaction do
Barbeque::JobExecution.create(message_id: @message.id, job_definition: job_definition, job_queue: @message_queue.job_queue).tap do |job_execution|
Barbeque::ExecutionLog.save_message(job_execution, @message)
@message_queue.delete_message(@message)
end
end
rescue ActiveRecord::RecordNotUnique => e
# There's a case where Barbeque receives message which was already received or deleted.
# https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/standard-queues.html
@message_queue.delete_message(@message)
raise DuplicatedExecution.new(e.message)
end
end
end
end
================================================
FILE: lib/barbeque/message_handler/job_retry.rb
================================================
require 'barbeque/execution_log'
require 'barbeque/executor'
require 'barbeque/slack_notifier'
module Barbeque
module MessageHandler
class MessageNotFound < StandardError; end
class JobRetry
# @param [Barbeque::Message::JobExecution] message
# @param [Barbeque::MessageQueue] message_queue
def initialize(message:, message_queue:)
@message = message
@message_queue = message_queue
end
def run
job_retry = create_job_retry
begin
Executor.create.start_retry(job_retry, job_envs)
rescue Exception => e
job_retry.update!(status: :error, finished_at: Time.now)
job_execution.update!(status: :error)
Barbeque::ExecutionLog.save_stdout_and_stderr(job_retry, '', "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
Barbeque::SlackNotifier.notify_job_retry(job_retry)
raise e
end
end
private
def job_execution
@job_execution ||= Barbeque::JobExecution.find_by!(message_id: @message.retry_message_id)
end
def job_envs
if job_execution.execution_log.nil?
raise MessageNotFound.new('failed to fetch retried message')
end
{
'BARBEQUE_JOB' => job_execution.job_definition.job,
'BARBEQUE_MESSAGE' => job_execution.execution_log['message'],
'BARBEQUE_MESSAGE_ID' => @message.retry_message_id,
'BARBEQUE_QUEUE_NAME' => @message_queue.job_queue.name,
'BARBEQUE_RETRY_COUNT' => job_execution.job_retries.count.to_s,
'BARBEQUE_SENT_TIMESTAMP' => @message.sent_timestamp,
}
end
def create_job_retry
Barbeque::JobRetry.transaction do
Barbeque::JobRetry.create(message_id: @message.id, job_execution: job_execution).tap do
@message_queue.delete_message(@message)
end
end
rescue ActiveRecord::RecordNotUnique => e
raise DuplicatedExecution.new(e.message)
end
end
end
end
================================================
FILE: lib/barbeque/message_handler/notification.rb
================================================
require 'barbeque/message_handler/job_execution'
module Barbeque
module MessageHandler
class Notification < JobExecution
# @param [Barbeque::Message::Notification] message
# @param [Barbeque::MessageQueue] message_queue
def initialize(message:, message_queue:)
@message = message
@message_queue = message_queue
subscription = SnsSubscription.find_by!(topic_arn: @message.topic_arn, job_queue_id: @message_queue.job_queue.id)
@message.set_params_from_subscription(subscription)
end
end
end
end
================================================
FILE: lib/barbeque/message_handler.rb
================================================
module Barbeque
module MessageHandler
class DuplicatedExecution < StandardError; end
end
end
require 'barbeque/message_handler/job_execution'
require 'barbeque/message_handler/job_retry'
require 'barbeque/message_handler/notification'
================================================
FILE: lib/barbeque/message_queue.rb
================================================
require 'aws-sdk-sqs'
require 'barbeque/config'
require 'barbeque/message'
module Barbeque
class MessageQueue
attr_reader :job_queue
def initialize(job_queue)
@job_queue = job_queue
@messages = []
@stop = false
end
# Receive a message from SQS queue.
# @return [Barbeque::Message::Base]
def dequeue
loop do
return nil if @stop
message = receive_message
if message
if message.valid?
return message
else
delete_message(message)
end
end
end
end
# Remove a message from SQS queue.
# @param [Barbeque::Message::Base] message
def delete_message(message)
client.delete_message(
queue_url: @job_queue.queue_url,
receipt_handle: message.receipt_handle,
)
end
def stop!
@stop = true
end
private
def receive_message
result = client.receive_message(
queue_url: @job_queue.queue_url,
wait_time_seconds: Barbeque.config.sqs_receive_message_wait_time,
max_number_of_messages: 1,
attribute_names: ['SentTimestamp'],
)
if result.messages[0]
Barbeque::Message.parse(result.messages[0])
end
end
def client
@client ||= Aws::SQS::Client.new
end
end
end
================================================
FILE: lib/barbeque/retry_poller.rb
================================================
require 'barbeque/exception_handler'
require 'barbeque/executor'
module Barbeque
class RetryPoller
def initialize(job_queue)
@job_queue = job_queue
@stop_requested = false
end
def run
Barbeque::JobRetry
.joins(:job_execution)
.running
.merge(Barbeque::JobExecution.where(job_queue: @job_queue))
.find_in_batches do |job_retries|
job_retries.shuffle.each do |job_retry|
if @stop_requested
return
end
job_retry.with_lock do
if job_retry.running?
poll(job_retry)
end
end
end
end
sleep 1
end
def stop
@stop_requested = true
end
private
def poll(job_retry)
Barbeque::ExceptionHandler.set_message_context(job_retry.message_id, nil)
executor = Executor.create
executor.poll_retry(job_retry)
end
end
end
================================================
FILE: lib/barbeque/runner.rb
================================================
require 'barbeque/config'
require 'barbeque/exception_handler'
require 'barbeque/execution_log'
require 'barbeque/message_handler'
require 'barbeque/message_queue'
module Barbeque
# Part of barbeque-worker.
# Runner dequeues a message from {MessageQueue} (Amazon SQS) and dispatches
# it to message handler.
class Runner
def initialize(job_queue)
@job_queue = job_queue
end
def run
keep_maximum_concurrent_executions
message = message_queue.dequeue
return unless message
Barbeque::ExceptionHandler.set_message_context(message.id, message.type)
handler = MessageHandler.const_get(message.type, false)
handler.new(message: message, message_queue: message_queue).run
end
def stop
message_queue.stop!
end
private
def message_queue
@message_queue ||= MessageQueue.new(@job_queue)
end
def keep_maximum_concurrent_executions
max_num = Barbeque.config.maximum_concurrent_executions
unless max_num
# nil means unlimited
return
end
loop do
current_num = @job_queue.job_executions.where(status: :running).count + Barbeque::JobRetry.where(status: :running).joins(job_execution: :job_queue).merge(Barbeque::JobQueue.where(id: @job_queue.id)).count
if current_num < max_num
return
end
interval = Barbeque.config.runner_wait_seconds
Rails.logger.info("#{current_num} executions are running but maximum_concurrent_executions is configured to #{max_num}. Waiting #{interval} seconds...")
sleep(interval)
end
end
end
end
================================================
FILE: lib/barbeque/slack_client.rb
================================================
require 'net/http'
require 'json'
require 'uri'
module Barbeque
class SlackClient
def initialize(channel)
@channel = channel
end
def notify_success(message)
post_slack(
attachments: [{
text: message,
color: 'good',
mrkdwn_in: ['text'],
}],
)
end
def notify_failure(message)
post_slack(
attachments: [{
text: message,
color: 'danger',
mrkdwn_in: ['text'],
}],
)
end
private
def post_slack(payload)
Net::HTTP.post_form(
endpoint_uri,
payload: default_payload.merge(payload).to_json,
)
end
def default_payload
{ link_names: 1, channel: @channel }
end
def endpoint_uri
@endpoint_uri ||= URI.parse(ENV['SLACK_WEBHOOK_URL'])
end
end
end
================================================
FILE: lib/barbeque/slack_notifier.rb
================================================
require 'barbeque/slack_client'
module Barbeque
module SlackNotifier
class << self
# @param [Barbeque::JobExecution] job_execution
def notify_job_execution(job_execution)
return if job_execution.slack_notification.nil?
client = Barbeque::SlackClient.new(job_execution.slack_notification.channel)
if job_execution.success?
if job_execution.slack_notification.notify_success
client.notify_success("*[SUCCESS]* Succeeded to execute #{job_execution_link(job_execution)}")
end
elsif job_execution.failed?
if should_notify_failure?(job_execution)
client.notify_failure(
"*[FAILURE]* Failed to execute #{job_execution_link(job_execution)}" \
" #{job_execution.slack_notification.failure_notification_text}"
)
end
else
client.notify_failure(
"*[ERROR]* Failed to execute #{job_execution_link(job_execution)}" \
" #{job_execution.slack_notification.failure_notification_text}"
)
end
end
# @param [Barbeque::JobRetry] job_retry
def notify_job_retry(job_retry)
return if job_retry.slack_notification.nil?
client = Barbeque::SlackClient.new(job_retry.slack_notification.channel)
if job_retry.success?
if job_retry.slack_notification.notify_success
client.notify_success("*[SUCCESS]* Succeeded to retry #{job_retry_link(job_retry)}")
end
elsif job_retry.failed?
if should_notify_failure?(job_retry.job_execution)
client.notify_failure(
"*[FAILURE]* Failed to retry #{job_retry_link(job_retry)}" \
" #{job_retry.slack_notification.failure_notification_text}"
)
end
else
client.notify_failure(
"*[ERROR]* Failed to retry #{job_retry_link(job_retry)}" \
" #{job_retry.slack_notification.failure_notification_text}"
)
end
end
private
def barbeque_host
ENV['BARBEQUE_HOST']
end
def job_execution_link(job_execution)
"<#{job_execution_url(job_execution)}|#{job_execution.job_definition.job} ##{job_execution.id}>"
end
def job_execution_url(job_execution)
Barbeque::Engine.routes.url_helpers.job_execution_url(job_execution, host: barbeque_host)
end
def job_retry_link(job_retry)
"<#{job_retry_url(job_retry)}|#{job_retry.job_definition.job}'s retry ##{job_retry.id}>"
end
def job_retry_url(job_retry)
Barbeque::Engine.routes.url_helpers.job_execution_job_retry_url(job_retry.job_execution, job_retry, host: barbeque_host)
end
def should_notify_failure?(job_execution_with_slack_notification)
unless job_execution_with_slack_notification.slack_notification.notify_failure_only_if_retry_limit_reached
return true
end
unless job_execution_with_slack_notification.job_definition.retry_config
return true
end
job_execution_with_slack_notification.job_definition.retry_config.retry_limit <= job_execution_with_slack_notification.job_retries.count
end
end
end
end
================================================
FILE: lib/barbeque/version.rb
================================================
module Barbeque
VERSION = '2.10.0'
end
================================================
FILE: lib/barbeque/worker.rb
================================================
require 'barbeque/exception_handler'
require 'barbeque/execution_poller'
require 'barbeque/retry_poller'
require 'barbeque/runner'
require 'serverengine'
module Barbeque
module Worker
DEFAULT_QUEUE = ENV['BARBEQUE_DEFAULT_QUEUE'] || 'default'
class UnexpectedMessageType < StandardError; end
def self.run(
worker_type: 'process',
workers: 4,
daemonize: false,
log: $stdout,
log_level: :info,
pid_path: '/tmp/barbeque_worker.pid',
supervisor: true
)
options = {
worker_type: worker_type,
workers: workers,
daemonize: daemonize,
log: log,
log_level: log_level,
pid_path: pid_path,
supervisor: supervisor,
}
worker = ServerEngine.create(nil, Barbeque::Worker, options)
worker.run
end
def initialize
queue_name = ENV['BARBEQUE_QUEUE'] || DEFAULT_QUEUE
queue = Barbeque::JobQueue.find_by!(name: queue_name)
@command =
case worker_id
when 0
ExecutionPoller.new(queue)
when 1
RetryPoller.new(queue)
else
Runner.new(queue)
end
end
def run
$0 = "barbeque-worker (worker_id=#{worker_id} command=#{@command.class.name})"
until @stop
begin
execute_command
rescue => e
Barbeque::ExceptionHandler.handle_exception(e)
end
Barbeque::ExceptionHandler.clear_context
end
end
def stop
@stop = true
@command.stop
end
def execute_command
@command.run
end
end
end
================================================
FILE: lib/barbeque.rb
================================================
require 'barbeque/docker_image'
require 'barbeque/engine'
require 'barbeque/execution_log'
================================================
FILE: lib/tasks/barbeque_tasks.rake
================================================
namespace :barbeque do
desc 'Start worker to execute jobs'
task worker: :environment do
ENV['BARBEQUE_QUEUE'] ||= 'default'
require 'barbeque/worker'
Barbeque::Worker.run(
workers: (ENV['BARBEQUE_WORKER_NUM'] || 4).to_i,
daemonize: ENV['DAEMONIZE_BARBEQUE'] == '1',
log: ENV['BARBEQUE_LOG_TO_STDOUT'] == '1' ? $stdout : Rails.root.join('log/barbeque_worker.log').to_s,
log_level: Rails.env.production? ? :info : :debug,
pid_path: Rails.root.join('tmp/pids/barbeque_worker.pid').to_s,
supervisor: Rails.env.production?,
)
end
end
================================================
FILE: spec/barbeque/config_builder_spec.rb
================================================
require 'rails_helper'
require 'barbeque/config'
describe Barbeque::ConfigBuilder do
describe '#build_config' do
it 'returns Barbeque::Config loaded from config/barbeque.yml' do
config = Barbeque.build_config
expect(config.executor).to eq('Docker')
end
context 'when it has no config' do
it 'returns default config' do
config = Barbeque.build_config('barbeque.empty')
expect(config.executor_options).to eq({})
end
end
context 'given erb' do
it 'evaluates barbeque.yml as erb' do
config = Barbeque.build_config('barbeque.erb')
expect(config.executor).to eq('FooBar')
end
end
end
end
================================================
FILE: spec/barbeque/config_spec.rb
================================================
require 'rails_helper'
require 'barbeque/config'
describe Barbeque::Config do
describe '#executor_options' do
it 'returns Hash whose keys are symbolized without symbolizing values' do
options = { 'foo' => { 'bar' => 'baz' } }
expect(Barbeque::Config.new(executor_options: options).executor_options).
to eq({ foo: { 'bar' => 'baz' } })
end
end
end
================================================
FILE: spec/barbeque/docker_image_spec.rb
================================================
require 'barbeque'
describe Barbeque::DockerImage do
it 'parses docker image name without a tag' do
image = Barbeque::DockerImage.new('cookpad')
expect(image.repository).to eq('cookpad')
expect(image.tag).to eq('latest')
end
it 'parses docker image name with a tag' do
image = Barbeque::DockerImage.new('cookpad-ruby:2.2')
expect(image.repository).to eq('cookpad-ruby')
expect(image.tag).to eq('2.2')
end
it 'parses docker image name with a host' do
image = Barbeque::DockerImage.new('docker.io/library/ruby')
expect(image.repository).to eq('library/ruby')
expect(image.tag).to eq('latest')
expect(image.registry).to eq('docker.io')
end
describe '#to_s' do
context 'with docker registry specified' do
let(:image_name) { 'cookpad-ruby:2.2' }
let(:docker_registry) { 'docker-registry-001:80' }
around do |example|
begin
orig, ENV['BARBEQUE_DOCKER_REGISTRY'] = ENV['BARBEQUE_DOCKER_REGISTRY'], docker_registry
example.run
ensure
ENV['BARBEQUE_DOCKER_REGISTRY'] = orig
end
end
it 'prepends docker registry' do
image = Barbeque::DockerImage.new(image_name)
expect(image.to_s).to eq("#{docker_registry}/#{image_name}")
end
end
end
end
================================================
FILE: spec/barbeque/exception_handler_spec.rb
================================================
require 'rails_helper'
require 'barbeque/exception_handler'
describe Barbeque::ExceptionHandler do
describe '.handle_exception' do
let(:exception) do
StandardError.new('something went wrong').tap do |e|
e.set_backtrace(['barbeque.rb:1'])
end
end
let(:handler) { 'RailsLogger' }
before do
allow(Barbeque).to receive_message_chain(:config, :exception_handler).and_return(handler)
end
it 'handles exception with configured handler' do
expect_any_instance_of(Barbeque::ExceptionHandler::RailsLogger).to receive(:handle_exception).with(exception)
Barbeque::ExceptionHandler.handle_exception(exception)
end
end
end
================================================
FILE: spec/barbeque/execution_log_spec.rb
================================================
require 'rails_helper'
require 'barbeque/execution_log'
describe Barbeque::ExecutionLog do
let(:job_execution) { create(:job_execution, status: status) }
let(:s3_client) { double('Aws::S3::Client') }
let(:message) { ['hello'] }
let(:stdout) { 'stdout' }
let(:stderr) { 'stderr' }
before do
allow(Barbeque::ExecutionLog).to receive(:s3_client).and_return(s3_client)
end
describe '.fetch' do
def make_s3_object(content)
Aws::S3::Types::GetObjectOutput.new(body: StringIO.new(content))
end
context 'when job_execution is finished' do
let(:status) { 'success' }
before do
allow(s3_client).to receive(:get_object).with(
key: "#{job_execution.app.name}/#{job_execution.job_definition.job}/#{job_execution.message_id}/message.json",
bucket: 'barbeque',
).and_return(make_s3_object(message.to_json))
allow(s3_client).to receive(:get_object).with(
key: "#{job_execution.app.name}/#{job_execution.job_definition.job}/#{job_execution.message_id}/stdout.txt",
bucket: 'barbeque',
).and_return(make_s3_object(stdout))
allow(s3_client).to receive(:get_object).with(
key: "#{job_execution.app.name}/#{job_execution.job_definition.job}/#{job_execution.message_id}/stderr.txt",
bucket: 'barbeque',
).and_return(make_s3_object(stderr))
end
it 'fetches log from S3 for the job_execution' do
expect(Barbeque::ExecutionLog.load(execution: job_execution)).to eq({
'message' => message.to_json,
'stdout' => stdout,
'stderr' => stderr,
})
end
end
context 'when job_execution is pending' do
let(:status) { 'pending' }
it 'returns empty hash' do
expect(Barbeque::ExecutionLog.load(execution: job_execution)).to eq({})
end
end
context 'when job_retry is given' do
let(:job_retry) { create(:job_retry, job_execution: job_execution) }
let(:status) { 'success' }
before do
allow(s3_client).to receive(:get_object).with(
key: "#{job_execution.app.name}/#{job_execution.job_definition.job}/#{job_retry.message_id}/message.json",
bucket: 'barbeque',
).and_raise(Aws::S3::Errors::NoSuchKey.new(nil, 'The specified key does not exist.'))
allow(s3_client).to receive(:get_object).with(
key: "#{job_execution.app.name}/#{job_execution.job_definition.job}/#{job_retry.message_id}/stdout.txt",
bucket: 'barbeque',
).and_return(make_s3_object(stdout))
allow(s3_client).to receive(:get_object).with(
key: "#{job_execution.app.name}/#{job_execution.job_definition.job}/#{job_retry.message_id}/stderr.txt",
bucket: 'barbeque',
).and_return(make_s3_object(stderr))
end
it 'fetches log from S3 for the job_retry' do
expect(Barbeque::ExecutionLog.load(execution: job_retry)).to eq({
'message' => nil,
'stdout' => stdout,
'stderr' => stderr,
})
end
end
end
end
================================================
FILE: spec/barbeque/execution_poller_spec.rb
================================================
require 'rails_helper'
require 'barbeque/execution_poller'
RSpec.describe Barbeque::ExecutionPoller do
let(:job_queue) { create(:job_queue) }
let(:execution_poller) { described_class.new(job_queue) }
let(:executor) { double('Barbeque::Executor::Docker') }
before do
allow(Barbeque::Executor::Docker).to receive(:new).and_return(executor)
end
describe '#run' do
context 'when there is a running job execution' do
let(:job_execution) { create(:job_execution, status: :running, job_queue: job_queue) }
it 'polls the job execution' do
expect(executor).to receive(:poll_execution).with(job_execution)
execution_poller.run
end
end
end
end
================================================
FILE: spec/barbeque/executor/docker_spec.rb
================================================
require 'rails_helper'
require 'barbeque/executor/docker'
RSpec.describe Barbeque::Executor::Docker do
let(:executor) { described_class.new }
let(:command) { ['rake', 'test'] }
let(:job_definition) { FactoryBot.create(:job_definition, command: ['rake', 'test']) }
let(:job_execution) { FactoryBot.create(:job_execution, job_definition: job_definition, status: :pending) }
let(:container_id) { '59efea4938e5d11ff6e70441d7442614dc1da014e64a8144c87a7608e27e240c' }
let(:sqs_client) { instance_double(Aws::SQS::Client) }
around do |example|
original = ENV['BARBEQUE_HOST']
ENV['BARBEQUE_HOST'] = 'https://barbeque'
example.run
ENV['BARBEQUE_HOST'] = original
end
before do
allow(Barbeque::MessageRetryingService).to receive(:sqs_client).and_return(sqs_client)
end
describe 'job_execution' do
describe '#start_execution' do
let(:status) { double('Process::Status', success?: true) }
let(:stdout) { container_id }
let(:stderr) { '' }
it 'starts Docker container' do
expect(Open3).to receive(:capture3).with('docker', 'run', '--detach', job_execution.job_definition.app.docker_image, 'rake', 'test').and_return([stdout, stderr, status])
executor.start_execution(job_execution, {})
expect(Barbeque::DockerContainer.where(message_id: job_execution.message_id, container_id: container_id)).to be_exist
end
it 'sets running status' do
expect(Open3).to receive(:capture3).with('docker', 'run', '--detach', job_execution.job_definition.app.docker_image, 'rake', 'test').and_return([stdout, stderr, status])
expect(job_execution).to be_pending
executor.start_execution(job_execution, {})
job_execution.reload
expect(job_execution).to be_running
end
context 'when docker-run fails' do
before do
expect(status).to receive(:success?).and_return(false)
end
it 'sets failed status' do
expect(Open3).to receive(:capture3).with('docker', 'run', '--detach', job_execution.job_definition.app.docker_image, 'rake', 'test').and_return([stdout, stderr, status])
expect(Barbeque::ExecutionLog).to receive(:try_save_stdout_and_stderr).with(job_execution, stdout, stderr)
executor.start_execution(job_execution, {})
job_execution.reload
expect(job_execution).to be_failed
end
context 'with retry_config' do
before do
FactoryBot.create(:retry_config, job_definition: job_definition)
end
it 'performs retry' do
expect(Open3).to receive(:capture3).with('docker', 'run', '--detach', job_execution.job_definition.app.docker_image, 'rake', 'test').and_return([stdout, stderr, status])
expect(Barbeque::ExecutionLog).to receive(:try_save_stdout_and_stderr).with(job_execution, stdout, stderr)
expect(Barbeque::MessageRetryingService.sqs_client).to receive(:send_message).with(queue_url: a_kind_of(String), message_body: a_kind_of(String), delay_seconds: a_kind_of(Integer))
executor.start_execution(job_execution, {})
job_execution.reload
expect(job_execution).to be_retried
end
end
end
end
describe '#poll_execution' do
let(:inspect_status) { double('Process::Status', success?: true) }
let(:container_info) do
{
'State' => {
'Status' => 'exited',
'FinishedAt' => '2017-07-11T09:17:32.013951633Z',
'ExitCode' => 0,
},
}
end
let(:stdout) { 'stdout' }
let(:stderr) { 'stderr' }
let(:log_status) { double('Process::Status', success?: true) }
before do
job_execution.update!(status: :running)
Barbeque::DockerContainer.create!(message_id: job_execution.message_id, container_id: container_id)
expect(Open3).to receive(:capture3).with('docker', 'inspect', container_id) {
[JSON.dump([container_info]), '', inspect_status]
}
end
context 'when job succeeds' do
it 'sets success status' do
expect(Open3).to receive(:capture3).with('docker', 'logs', container_id).and_return([stdout, stderr, log_status])
expect(Barbeque::ExecutionLog).to receive(:save_stdout_and_stderr).with(job_execution, stdout, stderr)
expect(job_execution).to be_running
executor.poll_execution(job_execution)
job_execution.reload
expect(job_execution).to be_success
end
context 'when successful slack_notification is configured' do
let(:slack_client) { double('Barbeque::SlackClient') }
let(:slack_notification) { FactoryBot.create(:slack_notification, notify_success: true) }
before do
job_execution.job_definition.update!(slack_notification: slack_notification)
allow(Barbeque::SlackClient).to receive(:new).with(slack_notification.channel).and_return(slack_client)
end
it 'sends slack notification' do
expect(Open3).to receive(:capture3).with('docker', 'logs', container_id).and_return([stdout, stderr, log_status])
expect(Barbeque::ExecutionLog).to receive(:save_stdout_and_stderr).with(job_execution, stdout, stderr)
expect(slack_client).to receive(:notify_success)
executor.poll_execution(job_execution)
end
end
end
context 'when job is running' do
before do
container_info['State']['Status'] = 'running'
container_info['State']['FinishedAt'] = '0001-01-01T00:00:00Z'
end
it 'does nothing' do
expect(job_execution).to be_running
executor.poll_execution(job_execution)
job_execution.reload
expect(job_execution).to be_running
end
end
context 'when job fails' do
before do
container_info['State']['ExitCode'] = 1
end
it 'sets failed status' do
expect(Open3).to receive(:capture3).with('docker', 'logs', container_id).and_return([stdout, stderr, log_status])
expect(Barbeque::ExecutionLog).to receive(:save_stdout_and_stderr).with(job_execution, stdout, stderr)
expect(job_execution).to be_running
executor.poll_execution(job_execution)
job_execution.reload
expect(job_execution).to be_failed
end
context 'when slack_notification is configured' do
let(:slack_client) { double('Barbeque::SlackClient') }
let(:slack_notification) { FactoryBot.create(:slack_notification, notify_success: false) }
before do
job_execution.job_definition.update!(slack_notification: slack_notification)
allow(Barbeque::SlackClient).to receive(:new).with(slack_notification.channel).and_return(slack_client)
end
it 'sends slack notification' do
expect(Open3).to receive(:capture3).with('docker', 'logs', container_id).and_return([stdout, stderr, log_status])
expect(Barbeque::ExecutionLog).to receive(:save_stdout_and_stderr).with(job_execution, stdout, stderr)
expect(slack_client).to receive(:notify_failure)
executor.poll_execution(job_execution)
end
end
context 'with retry_config' do
before do
FactoryBot.create(:retry_config, job_definition: job_definition)
end
it 'performs retry' do
expect(Open3).to receive(:capture3).with('docker', 'logs', container_id).and_return([stdout, stderr, log_status])
expect(Barbeque::ExecutionLog).to receive(:save_stdout_and_stderr).with(job_execution, stdout, stderr)
expect(Barbeque::MessageRetryingService.sqs_client).to receive(:send_message).with(queue_url: a_kind_of(String), message_body: a_kind_of(String), delay_seconds: a_kind_of(Integer))
expect(job_execution).to be_running
executor.poll_execution(job_execution)
job_execution.reload
expect(job_execution).to be_retried
end
end
context 'with retry_config and slack_notification (notify_failure_only_if_retry_limit_reached: false)' do
let(:slack_client) { double('Barbeque::SlackClient') }
let(:slack_notification) { FactoryBot.create(:slack_notification, notify_success: false) }
before do
job_execution.job_definition.update!(slack_notification: slack_notification)
allow(Barbeque::SlackClient).to receive(:new).with(slack_notification.channel).and_return(slack_client)
FactoryBot.create(:retry_config, job_definition: job_definition)
end
it 'sends slack notification' do
expect(Open3).to receive(:capture3).with('docker', 'logs', container_id).and_return([stdout, stderr, log_status])
expect(Barbeque::ExecutionLog).to receive(:save_stdout_and_stderr).with(job_execution, stdout, stderr)
expect(Barbeque::MessageRetryingService.sqs_client).to receive(:send_message).with(queue_url: a_kind_of(String), message_body: a_kind_of(String), delay_seconds: a_kind_of(Integer))
expect(slack_client).to receive(:notify_failure)
executor.poll_execution(job_execution)
end
end
context 'with retry_config and slack_notification (notify_failure_only_if_retry_limit_reached: true)' do
let(:slack_client) { double('Barbeque::SlackClient') }
let(:slack_notification) { FactoryBot.create(:slack_notification, notify_success: false, notify_failure_only_if_retry_limit_reached: true) }
before do
job_execution.job_definition.update!(slack_notification: slack_notification)
allow(Barbeque::SlackClient).to receive(:new).with(slack_notification.channel).and_return(slack_client)
FactoryBot.create(:retry_config, job_definition: job_definition)
end
it 'does not send slack notification' do
expect(Open3).to receive(:capture3).with('docker', 'logs', container_id).and_return([stdout, stderr, log_status])
expect(Barbeque::ExecutionLog).to receive(:save_stdout_and_stderr).with(job_execution, stdout, stderr)
expect(Barbeque::MessageRetryingService.sqs_client).to receive(:send_message).with(queue_url: a_kind_of(String), message_body: a_kind_of(String), delay_seconds: a_kind_of(Integer))
expect(slack_client).not_to receive(:notify_failure)
executor.poll_execution(job_execution)
end
end
end
end
end
describe 'job_retry' do
let(:job_retry) { FactoryBot.create(:job_retry, job_execution: job_execution, status: :pending) }
let(:status) { double('Process::Status', success?: true) }
let(:stdout) { container_id }
let(:stderr) { '' }
before do
job_execution.update!(status: :failed)
end
describe '#start_retry' do
it 'starts Docker container' do
expect(Open3).to receive(:capture3).with('docker', 'run', '--detach', job_execution.job_definition.app.docker_image, 'rake', 'test').and_return([stdout, stderr, status])
executor.start_retry(job_retry, {})
expect(Barbeque::DockerContainer.where(message_id: job_retry.message_id, container_id: container_id)).to be_exist
end
it 'sets running status' do
expect(Open3).to receive(:capture3).with('docker', 'run', '--detach', job_execution.job_definition.app.docker_image, 'rake', 'test').and_return([stdout, stderr, status])
expect(job_retry).to be_pending
expect(job_execution).to be_failed
executor.start_retry(job_retry, {})
job_retry.reload
job_execution.reload
expect(job_retry).to be_running
expect(job_execution).to be_retried
end
context 'when docker-run fails' do
before do
expect(status).to receive(:success?).and_return(false)
end
it 'sets failed status' do
expect(Open3).to receive(:capture3).with('docker', 'run', '--detach', job_execution.job_definition.app.docker_image, 'rake', 'test').and_return([stdout, stderr, status])
expect(Barbeque::ExecutionLog).to receive(:try_save_stdout_and_stderr).with(job_retry, stdout, stderr)
expect(job_retry).to be_pending
expect(job_execution).to be_failed
executor.start_retry(job_retry, {})
job_retry.reload
job_execution.reload
expect(job_retry).to be_failed
expect(job_execution).to be_failed
end
context 'with retry_config' do
before do
FactoryBot.create(:retry_config, job_definition: job_definition)
end
it 'performs retry' do
expect(Open3).to receive(:capture3).with('docker', 'run', '--detach', job_execution.job_definition.app.docker_image, 'rake', 'test').and_return([stdout, stderr, status])
expect(Barbeque::ExecutionLog).to receive(:try_save_stdout_and_stderr).with(job_retry, stdout, stderr)
expect(Barbeque::MessageRetryingService.sqs_client).to receive(:send_message).with(queue_url: a_kind_of(String), message_body: a_kind_of(String), delay_seconds: a_kind_of(Integer))
expect(job_retry).to be_pending
expect(job_execution).to be_failed
executor.start_retry(job_retry, {})
job_retry.reload
job_execution.reload
expect(job_retry).to be_failed
expect(job_execution).to be_retried
end
end
end
end
describe '#poll_retry' do
let(:inspect_status) { double('Process::Status', success?: true) }
let(:container_info) do
{
'State' => {
'Status' => 'exited',
'FinishedAt' => '2017-07-11T09:17:32.013951633Z',
'ExitCode' => 0,
},
}
end
let(:stdout) { 'stdout' }
let(:stderr) { 'stderr' }
let(:log_status) { double('Process::Status', success?: true) }
before do
job_execution.update!(status: :retried)
job_retry.update!(status: :running)
Barbeque::DockerContainer.create!(message_id: job_retry.message_id, container_id: container_id)
expect(Open3).to receive(:capture3).with('docker', 'inspect', container_id) {
[JSON.dump([container_info]), '', inspect_status]
}
end
context 'when retried job succeeds' do
it 'sets success status' do
expect(Open3).to receive(:capture3).with('docker', 'logs', container_id).and_return([stdout, stderr, log_status])
expect(Barbeque::ExecutionLog).to receive(:save_stdout_and_stderr).with(job_retry, stdout, stderr)
expect(job_retry).to be_running
expect(job_execution).to be_retried
executor.poll_retry(job_retry)
job_retry.reload
job_execution.reload
expect(job_retry).to be_success
expect(job_execution).to be_success
end
context 'when successful slack_notification is configured' do
let(:slack_client) { double('Barbeque::SlackClient') }
let(:slack_notification) { FactoryBot.create(:slack_notification, notify_success: true) }
before do
allow(Barbeque::SlackClient).to receive(:new).with(slack_notification.channel).and_return(slack_client)
end
it 'sends slack notification' do
expect(Open3).to receive(:capture3).with('docker', 'logs', container_id).and_return([stdout, stderr, log_status])
expect(Barbeque::ExecutionLog).to receive(:save_stdout_and_stderr).with(job_retry, stdout, stderr)
job_execution.job_definition.update!(slack_notification: slack_notification)
expect(slack_client).to receive(:notify_success)
executor.poll_retry(job_retry)
end
end
end
context 'when retried job is running' do
before do
container_info['State']['Status'] = 'running'
container_info['State']['FinishedAt'] = '0001-01-01T00:00:00Z'
end
it 'does nothing' do
expect(job_retry).to be_running
expect(job_execution).to be_retried
executor.poll_retry(job_retry)
job_retry.reload
job_execution.reload
expect(job_retry).to be_running
expect(job_execution).to be_retried
end
end
context 'when retried job fails' do
before do
container_info['State']['ExitCode'] = 1
end
it 'sets failed status' do
expect(Open3).to receive(:capture3).with('docker', 'logs', container_id).and_return([stdout, stderr, log_status])
expect(Barbeque::ExecutionLog).to receive(:save_stdout_and_stderr).with(job_retry, stdout, stderr)
expect(job_retry).to be_running
expect(job_execution).to be_retried
executor.poll_retry(job_retry)
job_retry.reload
job_execution.reload
expect(job_retry).to be_failed
expect(job_execution).to be_failed
end
context 'when slack_notification is configured' do
let(:slack_client) { double('Barbeque::SlackClient') }
let(:slack_notification) { FactoryBot.create(:slack_notification, notify_success: false) }
before do
job_execution.job_definition.update!(slack_notification: slack_notification)
allow(Barbeque::SlackClient).to receive(:new).with(slack_notification.channel).and_return(slack_client)
end
it 'sends slack n
gitextract_3xnxz7z0/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .rspec
├── CHANGELOG.md
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app/
│ ├── assets/
│ │ ├── config/
│ │ │ └── barbeque_manifest.js
│ │ ├── images/
│ │ │ └── barbeque/
│ │ │ └── .keep
│ │ ├── javascripts/
│ │ │ └── barbeque/
│ │ │ ├── application.js
│ │ │ ├── job_definitions.js
│ │ │ └── job_queues.js
│ │ └── stylesheets/
│ │ └── barbeque/
│ │ ├── application.css
│ │ └── job_definitions.css
│ ├── controllers/
│ │ └── barbeque/
│ │ ├── api/
│ │ │ ├── application_controller.rb
│ │ │ ├── job_executions_controller.rb
│ │ │ ├── job_retries_controller.rb
│ │ │ └── revision_locks_controller.rb
│ │ ├── application_controller.rb
│ │ ├── apps_controller.rb
│ │ ├── job_definitions_controller.rb
│ │ ├── job_executions_controller.rb
│ │ ├── job_queues_controller.rb
│ │ ├── job_retries_controller.rb
│ │ ├── monitors_controller.rb
│ │ └── sns_subscriptions_controller.rb
│ ├── helpers/
│ │ └── barbeque/
│ │ ├── application_helper.rb
│ │ ├── job_definitions_helper.rb
│ │ └── job_executions_helper.rb
│ ├── models/
│ │ ├── barbeque/
│ │ │ ├── api/
│ │ │ │ ├── application_resource.rb
│ │ │ │ ├── database_maintenance_resource.rb
│ │ │ │ ├── job_execution_resource.rb
│ │ │ │ ├── job_retry_resource.rb
│ │ │ │ └── revision_lock_resource.rb
│ │ │ ├── app.rb
│ │ │ ├── application_record.rb
│ │ │ ├── docker_container.rb
│ │ │ ├── ecs_hako_task.rb
│ │ │ ├── job_definition.rb
│ │ │ ├── job_execution.rb
│ │ │ ├── job_queue.rb
│ │ │ ├── job_retry.rb
│ │ │ ├── retry_config.rb
│ │ │ ├── slack_notification.rb
│ │ │ └── sns_subscription.rb
│ │ └── concerns/
│ │ └── .keep
│ ├── services/
│ │ └── barbeque/
│ │ ├── message_enqueuing_service.rb
│ │ ├── message_retrying_service.rb
│ │ └── sns_subscription_service.rb
│ └── views/
│ ├── barbeque/
│ │ ├── apps/
│ │ │ ├── _form.html.haml
│ │ │ ├── edit.html.haml
│ │ │ ├── index.html.haml
│ │ │ ├── new.html.haml
│ │ │ └── show.html.haml
│ │ ├── job_definitions/
│ │ │ ├── _form.html.haml
│ │ │ ├── _retry_configuration_field.html.haml
│ │ │ ├── _slack_notification_field.html.haml
│ │ │ ├── edit.html.haml
│ │ │ ├── index.html.haml
│ │ │ ├── new.html.haml
│ │ │ ├── show.html.haml
│ │ │ └── stats.html.haml
│ │ ├── job_executions/
│ │ │ └── show.html.haml
│ │ ├── job_queues/
│ │ │ ├── _form.html.haml
│ │ │ ├── edit.html.haml
│ │ │ ├── index.html.haml
│ │ │ ├── new.html.haml
│ │ │ └── show.html.haml
│ │ ├── job_retries/
│ │ │ └── show.html.haml
│ │ ├── monitors/
│ │ │ └── index.html.haml
│ │ └── sns_subscriptions/
│ │ ├── _form.html.haml
│ │ ├── edit.html.haml
│ │ ├── index.html.haml
│ │ ├── new.html.haml
│ │ └── show.html.haml
│ └── layouts/
│ └── barbeque/
│ ├── _header.html.haml
│ ├── _sidebar.html.haml
│ ├── application.html.haml
│ ├── apps.html.haml
│ ├── job_definitions.html.haml
│ ├── job_executions.html.haml
│ ├── job_queues.html.haml
│ ├── job_retries.html.haml
│ ├── monitors.html.haml
│ └── sns_subscriptions.html.haml
├── barbeque.gemspec
├── bin/
│ └── rails
├── compose.yaml
├── config/
│ ├── initializers/
│ │ └── garage.rb
│ └── routes.rb
├── db/
│ └── migrate/
│ ├── 20160217020910_create_job_queues.rb
│ ├── 20160219010912_create_job_executions.rb
│ ├── 20160223060807_create_apps.rb
│ ├── 20160223183348_create_job_definitions.rb
│ ├── 20160225020801_add_job_definition_id_to_job_executions.rb
│ ├── 20160412083604_add_finished_at_to_job_executions.rb
│ ├── 20160415043427_create_slack_notifications.rb
│ ├── 20160509041452_add_job_queue_id_to_job_executions.rb
│ ├── 20160516041710_create_job_retries.rb
│ ├── 20160829023237_prefix_barbeque_to_tables.rb
│ ├── 20170420030157_create_barbeque_sns_subscriptions.rb
│ ├── 20170711085157_create_barbeque_docker_containers.rb
│ ├── 20170712075449_create_barbeque_ecs_hako_tasks.rb
│ ├── 20170724025542_add_index_to_job_execution_status.rb
│ ├── 20180411070937_add_index_to_barbeque_job_executions_created_at.rb
│ ├── 20190221050714_create_barbeque_retry_configs.rb
│ ├── 20190311034445_add_notify_failure_only_if_retry_limit_reached_to_barbeque_slack_notifications.rb
│ ├── 20190315052951_change_barbeque_retry_configs_base_delay_default_value.rb
│ ├── 20191029105530_change_job_name_to_case_sensitive.rb
│ └── 20240415080757_fix_collations.rb
├── doc/
│ ├── api/
│ │ ├── job_executions.md
│ │ ├── job_retries.md
│ │ └── revision_locks.md
│ └── toc.md
├── lib/
│ ├── barbeque/
│ │ ├── config.rb
│ │ ├── docker_image.rb
│ │ ├── engine.rb
│ │ ├── exception_handler.rb
│ │ ├── execution_log.rb
│ │ ├── execution_poller.rb
│ │ ├── executor/
│ │ │ ├── docker.rb
│ │ │ └── hako.rb
│ │ ├── executor.rb
│ │ ├── hako_s3_client.rb
│ │ ├── maintenance.rb
│ │ ├── message/
│ │ │ ├── base.rb
│ │ │ ├── invalid_message.rb
│ │ │ ├── job_execution.rb
│ │ │ ├── job_retry.rb
│ │ │ └── notification.rb
│ │ ├── message.rb
│ │ ├── message_handler/
│ │ │ ├── job_execution.rb
│ │ │ ├── job_retry.rb
│ │ │ └── notification.rb
│ │ ├── message_handler.rb
│ │ ├── message_queue.rb
│ │ ├── retry_poller.rb
│ │ ├── runner.rb
│ │ ├── slack_client.rb
│ │ ├── slack_notifier.rb
│ │ ├── version.rb
│ │ └── worker.rb
│ ├── barbeque.rb
│ └── tasks/
│ └── barbeque_tasks.rake
├── spec/
│ ├── barbeque/
│ │ ├── config_builder_spec.rb
│ │ ├── config_spec.rb
│ │ ├── docker_image_spec.rb
│ │ ├── exception_handler_spec.rb
│ │ ├── execution_log_spec.rb
│ │ ├── execution_poller_spec.rb
│ │ ├── executor/
│ │ │ ├── docker_spec.rb
│ │ │ └── hako_spec.rb
│ │ ├── executor_spec.rb
│ │ ├── message_handler/
│ │ │ ├── job_execution_spec.rb
│ │ │ ├── job_retry_spec.rb
│ │ │ └── notification_spec.rb
│ │ ├── message_queue_spec.rb
│ │ ├── message_spec.rb
│ │ ├── retry_poller_spec.rb
│ │ ├── runner_spec.rb
│ │ └── worker_spec.rb
│ ├── controllers/
│ │ └── barbeque/
│ │ ├── apps_controller_spec.rb
│ │ ├── job_definitions_controller_spec.rb
│ │ ├── job_executions_controller_spec.rb
│ │ ├── job_queues_controller_spec.rb
│ │ ├── job_retries_controller_spec.rb
│ │ └── sns_subscriptions_controller_spec.rb
│ ├── dummy/
│ │ ├── .gitignore
│ │ ├── Rakefile
│ │ ├── app/
│ │ │ ├── assets/
│ │ │ │ ├── config/
│ │ │ │ │ └── manifest.js
│ │ │ │ ├── images/
│ │ │ │ │ └── .keep
│ │ │ │ ├── javascripts/
│ │ │ │ │ ├── application.js
│ │ │ │ │ └── channels/
│ │ │ │ │ └── .keep
│ │ │ │ └── stylesheets/
│ │ │ │ └── application.css
│ │ │ ├── controllers/
│ │ │ │ ├── application_controller.rb
│ │ │ │ └── concerns/
│ │ │ │ └── .keep
│ │ │ ├── helpers/
│ │ │ │ └── application_helper.rb
│ │ │ ├── models/
│ │ │ │ └── concerns/
│ │ │ │ └── .keep
│ │ │ └── views/
│ │ │ └── layouts/
│ │ │ ├── application.html.erb
│ │ │ ├── mailer.html.erb
│ │ │ └── mailer.text.erb
│ │ ├── bin/
│ │ │ ├── bundle
│ │ │ ├── rails
│ │ │ ├── rake
│ │ │ ├── setup
│ │ │ ├── spring
│ │ │ ├── update
│ │ │ └── yarn
│ │ ├── config/
│ │ │ ├── application.rb
│ │ │ ├── barbeque.empty.yml
│ │ │ ├── barbeque.erb.yml
│ │ │ ├── barbeque.hako.yml
│ │ │ ├── barbeque.yml
│ │ │ ├── boot.rb
│ │ │ ├── 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
│ │ │ │ ├── permissions_policy.rb
│ │ │ │ ├── session_store.rb
│ │ │ │ └── wrap_parameters.rb
│ │ │ ├── locales/
│ │ │ │ └── en.yml
│ │ │ ├── puma.rb
│ │ │ ├── routes.rb
│ │ │ ├── secrets.yml
│ │ │ ├── spring.rb
│ │ │ └── storage.yml
│ │ ├── config.ru
│ │ ├── db/
│ │ │ ├── schema.rb
│ │ │ └── seeds.rb
│ │ ├── lib/
│ │ │ ├── assets/
│ │ │ │ └── .keep
│ │ │ └── tasks/
│ │ │ └── .keep
│ │ ├── log/
│ │ │ └── .keep
│ │ ├── public/
│ │ │ ├── 404.html
│ │ │ ├── 422.html
│ │ │ ├── 500.html
│ │ │ └── robots.txt
│ │ ├── tmp/
│ │ │ └── .keep
│ │ └── vendor/
│ │ └── assets/
│ │ ├── javascripts/
│ │ │ └── .keep
│ │ └── stylesheets/
│ │ └── .keep
│ ├── factories/
│ │ ├── app.rb
│ │ ├── job_definition.rb
│ │ ├── job_execution.rb
│ │ ├── job_queue.rb
│ │ ├── job_retry.rb
│ │ ├── retry_config.rb
│ │ ├── slack_notifications.rb
│ │ └── sns_subscription.rb
│ ├── models/
│ │ └── barbeque/
│ │ ├── job_definition_spec.rb
│ │ └── retry_config_spec.rb
│ ├── rails_helper.rb
│ ├── requests/
│ │ └── api/
│ │ ├── job_executions_spec.rb
│ │ ├── job_retries_spec.rb
│ │ └── revision_locks_spec.rb
│ ├── services/
│ │ └── message_enqueuing_service_spec.rb
│ └── spec_helper.rb
├── tools/
│ └── s3-log-migrator.rb
└── vendor/
└── assets/
├── javascripts/
│ ├── adminlte.js
│ ├── bootstrap.js
│ └── plotly-basic.js
└── stylesheets/
├── AdminLTE.css
├── bootstrap.css
└── skins/
└── skin-blue.css
SYMBOL INDEX (1455 symbols across 91 files)
FILE: app/controllers/barbeque/api/application_controller.rb
class Barbeque::Api::ApplicationController (line 3) | class Barbeque::Api::ApplicationController < ActionController::API
method respond_with_error (line 21) | def respond_with_error(status_code, error_code, message)
method force_json_format (line 26) | def force_json_format
FILE: app/controllers/barbeque/api/job_executions_controller.rb
class Barbeque::Api::JobExecutionsController (line 3) | class Barbeque::Api::JobExecutionsController < Barbeque::Api::Applicatio...
method require_resources (line 20) | def require_resources
method require_resource (line 24) | def require_resource
method create_resource (line 36) | def create_resource
method enqueue_message (line 43) | def enqueue_message
method location (line 54) | def location
method respond_with_resource_options (line 62) | def respond_with_resource_options
FILE: app/controllers/barbeque/api/job_retries_controller.rb
class Barbeque::Api::JobRetriesController (line 1) | class Barbeque::Api::JobRetriesController < Barbeque::Api::ApplicationCo...
method require_resources (line 13) | def require_resources
method create_resource (line 17) | def create_resource
method retry_message (line 22) | def retry_message
FILE: app/controllers/barbeque/api/revision_locks_controller.rb
class Barbeque::Api::RevisionLocksController (line 1) | class Barbeque::Api::RevisionLocksController < Barbeque::Api::Applicatio...
method require_resources (line 10) | def require_resources
method require_resource (line 14) | def require_resource
method create_resource (line 18) | def create_resource
method destroy_resource (line 27) | def destroy_resource
FILE: app/controllers/barbeque/application_controller.rb
type Barbeque (line 1) | module Barbeque
class ApplicationController (line 2) | class ApplicationController < ActionController::Base
FILE: app/controllers/barbeque/apps_controller.rb
class Barbeque::AppsController (line 1) | class Barbeque::AppsController < Barbeque::ApplicationController
method index (line 2) | def index
method show (line 6) | def show
method new (line 10) | def new
method edit (line 14) | def edit
method create (line 18) | def create
method update (line 28) | def update
method destroy (line 38) | def destroy
FILE: app/controllers/barbeque/job_definitions_controller.rb
class Barbeque::JobDefinitionsController (line 1) | class Barbeque::JobDefinitionsController < Barbeque::ApplicationController
method index (line 2) | def index
method show (line 6) | def show
method new (line 16) | def new
method edit (line 25) | def edit
method create (line 35) | def create
method update (line 46) | def update
method destroy (line 60) | def destroy
method stats (line 69) | def stats
method execution_stats (line 74) | def execution_stats
method slack_notification_params (line 83) | def slack_notification_params
method retry_config_params (line 87) | def retry_config_params
method command_array (line 91) | def command_array
method new_job_definition_params (line 95) | def new_job_definition_params
FILE: app/controllers/barbeque/job_executions_controller.rb
class Barbeque::JobExecutionsController (line 1) | class Barbeque::JobExecutionsController < Barbeque::ApplicationController
method show (line 4) | def show
method retry (line 20) | def retry
FILE: app/controllers/barbeque/job_queues_controller.rb
class Barbeque::JobQueuesController (line 5) | class Barbeque::JobQueuesController < Barbeque::ApplicationController
method index (line 6) | def index
method show (line 10) | def show
method new (line 14) | def new
method edit (line 18) | def edit
method create (line 22) | def create
method update (line 33) | def update
method destroy (line 43) | def destroy
method sqs_attributes (line 49) | def sqs_attributes
method sqs_metrics (line 89) | def sqs_metrics
method create_queue (line 134) | def create_queue(job_queue)
method sqs_client (line 143) | def self.sqs_client
method cloudwatch_client (line 147) | def self.cloudwatch_client
method queue_name_from_arn (line 151) | def queue_name_from_arn(arn)
method compute_minimum_period (line 155) | def compute_minimum_period(start_time, end_time, maximum_datapoint: 1440)
FILE: app/controllers/barbeque/job_retries_controller.rb
class Barbeque::JobRetriesController (line 1) | class Barbeque::JobRetriesController < Barbeque::ApplicationController
method show (line 2) | def show
FILE: app/controllers/barbeque/monitors_controller.rb
class Barbeque::MonitorsController (line 1) | class Barbeque::MonitorsController < Barbeque::ApplicationController
method index (line 2) | def index
FILE: app/controllers/barbeque/sns_subscriptions_controller.rb
class Barbeque::SnsSubscriptionsController (line 1) | class Barbeque::SnsSubscriptionsController < Barbeque::ApplicationContro...
method index (line 2) | def index
method show (line 6) | def show
method new (line 10) | def new
method edit (line 14) | def edit
method create (line 18) | def create
method update (line 27) | def update
method destroy (line 36) | def destroy
FILE: app/helpers/barbeque/application_helper.rb
type Barbeque (line 1) | module Barbeque
type ApplicationHelper (line 2) | module ApplicationHelper
FILE: app/helpers/barbeque/job_definitions_helper.rb
type Barbeque::JobDefinitionsHelper (line 1) | module Barbeque::JobDefinitionsHelper
function distance_of_time (line 2) | def distance_of_time(from, to)
FILE: app/helpers/barbeque/job_executions_helper.rb
type Barbeque::JobExecutionsHelper (line 1) | module Barbeque::JobExecutionsHelper
function status_label (line 2) | def status_label(status)
FILE: app/models/barbeque/api/application_resource.rb
class Barbeque::Api::ApplicationResource (line 3) | class Barbeque::Api::ApplicationResource
method initialize (line 9) | def initialize(model = nil)
FILE: app/models/barbeque/api/database_maintenance_resource.rb
class Barbeque::Api::DatabaseMaintenanceResource (line 1) | class Barbeque::Api::DatabaseMaintenanceResource
method initialize (line 8) | def initialize(exception)
FILE: app/models/barbeque/api/job_execution_resource.rb
class Barbeque::Api::JobExecutionResource (line 1) | class Barbeque::Api::JobExecutionResource < Barbeque::Api::ApplicationRe...
method html_url (line 10) | def html_url
method message (line 18) | def message
FILE: app/models/barbeque/api/job_retry_resource.rb
class Barbeque::Api::JobRetryResource (line 1) | class Barbeque::Api::JobRetryResource < Barbeque::Api::ApplicationResource
FILE: app/models/barbeque/api/revision_lock_resource.rb
class Barbeque::Api::RevisionLockResource (line 1) | class Barbeque::Api::RevisionLockResource < Barbeque::Api::ApplicationRe...
method revision (line 4) | def revision
FILE: app/models/barbeque/app.rb
class Barbeque::App (line 1) | class Barbeque::App < Barbeque::ApplicationRecord
FILE: app/models/barbeque/application_record.rb
type Barbeque (line 1) | module Barbeque
class ApplicationRecord (line 2) | class ApplicationRecord < ActiveRecord::Base
FILE: app/models/barbeque/docker_container.rb
class Barbeque::DockerContainer (line 1) | class Barbeque::DockerContainer < Barbeque::ApplicationRecord
FILE: app/models/barbeque/ecs_hako_task.rb
class Barbeque::EcsHakoTask (line 1) | class Barbeque::EcsHakoTask < Barbeque::ApplicationRecord
FILE: app/models/barbeque/job_definition.rb
class Barbeque::JobDefinition (line 1) | class Barbeque::JobDefinition < Barbeque::ApplicationRecord
method execution_stats (line 20) | def execution_stats(from, to)
FILE: app/models/barbeque/job_execution.rb
class Barbeque::JobExecution (line 1) | class Barbeque::JobExecution < Barbeque::ApplicationRecord
method execution_log (line 20) | def execution_log
method retryable? (line 24) | def retryable?
method to_param (line 28) | def to_param
method retry_if_possible! (line 32) | def retry_if_possible!
FILE: app/models/barbeque/job_queue.rb
class Barbeque::JobQueue (line 3) | class Barbeque::JobQueue < Barbeque::ApplicationRecord
method sqs_queue_name (line 16) | def sqs_queue_name
method queue_url_from_name (line 30) | def self.queue_url_from_name(name)
FILE: app/models/barbeque/job_retry.rb
class Barbeque::JobRetry (line 1) | class Barbeque::JobRetry < Barbeque::ApplicationRecord
method execution_log (line 17) | def execution_log
method to_resource (line 21) | def to_resource
FILE: app/models/barbeque/retry_config.rb
class Barbeque::RetryConfig (line 1) | class Barbeque::RetryConfig < Barbeque::ApplicationRecord
method should_retry? (line 8) | def should_retry?(retries)
method delay_seconds (line 14) | def delay_seconds(retries)
FILE: app/models/barbeque/slack_notification.rb
class Barbeque::SlackNotification (line 1) | class Barbeque::SlackNotification < Barbeque::ApplicationRecord
FILE: app/models/barbeque/sns_subscription.rb
type Barbeque (line 1) | module Barbeque
class SnsSubscription (line 2) | class SnsSubscription < ApplicationRecord
method topic_region (line 12) | def topic_region
method topic_arn_is_formatted (line 18) | def topic_arn_is_formatted
FILE: app/services/barbeque/message_enqueuing_service.rb
class Barbeque::MessageEnqueuingService (line 3) | class Barbeque::MessageEnqueuingService
class BadRequest (line 7) | class BadRequest < StandardError
method sqs_client (line 10) | def self.sqs_client
method initialize (line 19) | def initialize(application:, job:, message:, queue: nil, delay_seconds...
method run (line 28) | def run
method build_message (line 47) | def build_message
FILE: app/services/barbeque/message_retrying_service.rb
class Barbeque::MessageRetryingService (line 3) | class Barbeque::MessageRetryingService
method sqs_client (line 6) | def self.sqs_client
method initialize (line 10) | def initialize(message_id:, delay_seconds: nil)
method run (line 15) | def run
method build_message (line 26) | def build_message
FILE: app/services/barbeque/sns_subscription_service.rb
class Barbeque::SnsSubscriptionService (line 4) | class Barbeque::SnsSubscriptionService
method sqs_client (line 5) | def self.sqs_client
method sns_client (line 9) | def self.sns_client(region:)
method subscribe (line 17) | def subscribe(sns_subscription)
method unsubscribe (line 37) | def unsubscribe(sns_subscription)
method sqs_client (line 46) | def sqs_client
method sns_client (line 50) | def sns_client(region:)
method update_sqs_policy! (line 55) | def update_sqs_policy!(sns_subscription)
method generate_policy (line 78) | def generate_policy(queue_arn:, topic_arns:)
method subscribe_topic! (line 96) | def subscribe_topic!(sns_subscription)
method unsubscribe_topic! (line 111) | def unsubscribe_topic!(sns_subscription)
FILE: db/migrate/20160217020910_create_job_queues.rb
class CreateJobQueues (line 1) | class CreateJobQueues < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20160219010912_create_job_executions.rb
class CreateJobExecutions (line 1) | class CreateJobExecutions < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20160223060807_create_apps.rb
class CreateApps (line 1) | class CreateApps < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20160223183348_create_job_definitions.rb
class CreateJobDefinitions (line 1) | class CreateJobDefinitions < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20160225020801_add_job_definition_id_to_job_executions.rb
class AddJobDefinitionIdToJobExecutions (line 1) | class AddJobDefinitionIdToJobExecutions < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20160412083604_add_finished_at_to_job_executions.rb
class AddFinishedAtToJobExecutions (line 1) | class AddFinishedAtToJobExecutions < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20160415043427_create_slack_notifications.rb
class CreateSlackNotifications (line 1) | class CreateSlackNotifications < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20160509041452_add_job_queue_id_to_job_executions.rb
class AddJobQueueIdToJobExecutions (line 1) | class AddJobQueueIdToJobExecutions < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20160516041710_create_job_retries.rb
class CreateJobRetries (line 1) | class CreateJobRetries < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20160829023237_prefix_barbeque_to_tables.rb
class PrefixBarbequeToTables (line 1) | class PrefixBarbequeToTables < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20170420030157_create_barbeque_sns_subscriptions.rb
class CreateBarbequeSnsSubscriptions (line 1) | class CreateBarbequeSnsSubscriptions < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20170711085157_create_barbeque_docker_containers.rb
class CreateBarbequeDockerContainers (line 1) | class CreateBarbequeDockerContainers < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20170712075449_create_barbeque_ecs_hako_tasks.rb
class CreateBarbequeEcsHakoTasks (line 1) | class CreateBarbequeEcsHakoTasks < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20170724025542_add_index_to_job_execution_status.rb
class AddIndexToJobExecutionStatus (line 1) | class AddIndexToJobExecutionStatus < ActiveRecord::Migration[5.0]
method change (line 2) | def change
FILE: db/migrate/20180411070937_add_index_to_barbeque_job_executions_created_at.rb
class AddIndexToBarbequeJobExecutionsCreatedAt (line 1) | class AddIndexToBarbequeJobExecutionsCreatedAt < ActiveRecord::Migration...
method change (line 2) | def change
FILE: db/migrate/20190221050714_create_barbeque_retry_configs.rb
class CreateBarbequeRetryConfigs (line 1) | class CreateBarbequeRetryConfigs < ActiveRecord::Migration[5.2]
method change (line 2) | def change
FILE: db/migrate/20190311034445_add_notify_failure_only_if_retry_limit_reached_to_barbeque_slack_notifications.rb
class AddNotifyFailureOnlyIfRetryLimitReachedToBarbequeSlackNotifications (line 1) | class AddNotifyFailureOnlyIfRetryLimitReachedToBarbequeSlackNotification...
method change (line 2) | def change
FILE: db/migrate/20190315052951_change_barbeque_retry_configs_base_delay_default_value.rb
class ChangeBarbequeRetryConfigsBaseDelayDefaultValue (line 1) | class ChangeBarbequeRetryConfigsBaseDelayDefaultValue < ActiveRecord::Mi...
method up (line 2) | def up
method down (line 6) | def down
FILE: db/migrate/20191029105530_change_job_name_to_case_sensitive.rb
class ChangeJobNameToCaseSensitive (line 1) | class ChangeJobNameToCaseSensitive < ActiveRecord::Migration[5.2]
method up (line 2) | def up
method down (line 6) | def down
FILE: db/migrate/20240415080757_fix_collations.rb
class FixCollations (line 8) | class FixCollations < ActiveRecord::Migration[6.1]
method change (line 9) | def change
FILE: lib/barbeque/config.rb
type Barbeque (line 4) | module Barbeque
class Config (line 5) | class Config
method initialize (line 8) | def initialize(options = {})
type ConfigBuilder (line 20) | module ConfigBuilder
function config (line 32) | def config
function build_config (line 36) | def build_config(config_name = 'barbeque')
FILE: lib/barbeque/docker_image.rb
type Barbeque (line 1) | module Barbeque
class DockerImage (line 2) | class DockerImage
method initialize (line 5) | def initialize(str)
method to_s (line 16) | def to_s
FILE: lib/barbeque/engine.rb
type Barbeque (line 4) | module Barbeque
class Engine (line 5) | class Engine < ::Rails::Engine
FILE: lib/barbeque/exception_handler.rb
type Barbeque (line 3) | module Barbeque
type ExceptionHandler (line 4) | module ExceptionHandler
function handler (line 10) | def handler
class RailsLogger (line 15) | class RailsLogger
method initialize (line 16) | def initialize
method clear_context (line 20) | def clear_context
method set_message_context (line 27) | def set_message_context(message_id, message_type)
method handle_exception (line 33) | def handle_exception(e)
class Raven (line 38) | class Raven
method clear_context (line 39) | def clear_context
method set_message_context (line 45) | def set_message_context(message_id, message_type)
method handle_exception (line 50) | def handle_exception(e)
class Sentry (line 55) | class Sentry
method initialize (line 56) | def initialize
method clear_context (line 60) | def clear_context
method set_message_context (line 67) | def set_message_context(message_id, message_type)
method handle_exception (line 77) | def handle_exception(e)
FILE: lib/barbeque/execution_log.rb
type Barbeque (line 6) | module Barbeque
class ExecutionLog (line 7) | class ExecutionLog
method s3_client (line 13) | def s3_client
method save_message (line 20) | def save_message(execution, message)
method save_stdout_and_stderr (line 27) | def save_stdout_and_stderr(execution, stdout, stderr)
method try_save_stdout_and_stderr (line 35) | def try_save_stdout_and_stderr(execution, stdout, stderr)
method load (line 43) | def load(execution:)
method s3_bucket_name (line 62) | def s3_bucket_name
method s3_key_for (line 68) | def s3_key_for(execution, filename)
method get (line 75) | def get(execution, filename)
method put (line 88) | def put(execution, filename, content)
FILE: lib/barbeque/execution_poller.rb
type Barbeque (line 4) | module Barbeque
class ExecutionPoller (line 5) | class ExecutionPoller
method initialize (line 6) | def initialize(job_queue)
method run (line 11) | def run
method stop (line 27) | def stop
method poll (line 33) | def poll(job_execution)
FILE: lib/barbeque/executor.rb
type Barbeque (line 5) | module Barbeque
type Executor (line 23) | module Executor
function create (line 24) | def self.create
FILE: lib/barbeque/executor/docker.rb
type Barbeque (line 6) | module Barbeque
type Executor (line 7) | module Executor
class Docker (line 8) | class Docker
class DockerCommandError (line 9) | class DockerCommandError < StandardError
method initialize (line 12) | def initialize(**)
method start_execution (line 17) | def start_execution(job_execution, envs)
method start_retry (line 34) | def start_retry(job_retry, envs)
method poll_execution (line 57) | def poll_execution(job_execution)
method poll_retry (line 75) | def poll_retry(job_retry)
method build_docker_run_command (line 102) | def build_docker_run_command(docker_image, command, envs)
method env_options (line 106) | def env_options(envs)
method inspect_container (line 114) | def inspect_container(container_id)
method get_logs (line 130) | def get_logs(container_id)
FILE: lib/barbeque/executor/hako.rb
type Barbeque (line 7) | module Barbeque
type Executor (line 8) | module Executor
class Hako (line 9) | class Hako
class HakoCommandError (line 10) | class HakoCommandError < StandardError
method initialize (line 19) | def initialize(hako_dir:, hako_env: {}, yaml_dir: nil, definition_...
method start_execution (line 36) | def start_execution(job_execution, envs)
method start_retry (line 55) | def start_retry(job_retry, envs)
method poll_execution (line 80) | def poll_execution(job_execution)
method poll_retry (line 99) | def poll_retry(job_retry)
method build_hako_oneshot_command (line 123) | def build_hako_oneshot_command(docker_image, command, envs)
method env_options (line 139) | def env_options(envs)
method extract_task_info (line 145) | def extract_task_info(stdout)
FILE: lib/barbeque/hako_s3_client.rb
type Barbeque (line 5) | module Barbeque
class HakoS3Client (line 6) | class HakoS3Client
method initialize (line 8) | def initialize(oneshot_notification_prefix)
method s3_key_for_stopped_result (line 17) | def s3_key_for_stopped_result(hako_task)
method s3_client (line 22) | def s3_client
method get_stopped_result (line 28) | def get_stopped_result(hako_task)
FILE: lib/barbeque/maintenance.rb
type Barbeque (line 1) | module Barbeque
type Maintenance (line 2) | module Maintenance
function database_maintenance_mode? (line 3) | def self.database_maintenance_mode?
FILE: lib/barbeque/message.rb
type Barbeque (line 8) | module Barbeque
type Message (line 9) | module Message
function parse (line 13) | def parse(raw_message)
function find_class (line 24) | def find_class(type)
FILE: lib/barbeque/message/base.rb
type Barbeque (line 1) | module Barbeque
type Message (line 2) | module Message
class Base (line 4) | class Base
method initialize (line 12) | def initialize(raw_message, message_body)
method valid? (line 20) | def valid?
method assign_body (line 26) | def assign_body(message_body)
FILE: lib/barbeque/message/invalid_message.rb
type Barbeque (line 3) | module Barbeque
type Message (line 4) | module Message
class InvalidMessage (line 5) | class InvalidMessage < Base
method valid? (line 6) | def valid?
FILE: lib/barbeque/message/job_execution.rb
type Barbeque (line 3) | module Barbeque
type Message (line 4) | module Message
class JobExecution (line 5) | class JobExecution < Base
method assign_body (line 12) | def assign_body(message_body)
FILE: lib/barbeque/message/job_retry.rb
type Barbeque (line 3) | module Barbeque
type Message (line 4) | module Message
class JobRetry (line 5) | class JobRetry < Base
method assign_body (line 10) | def assign_body(message_body)
FILE: lib/barbeque/message/notification.rb
type Barbeque (line 3) | module Barbeque
type Message (line 4) | module Message
class Notification (line 5) | class Notification < Base
method set_params_from_subscription (line 13) | def set_params_from_subscription(subscription)
method assign_body (line 21) | def assign_body(message_body)
FILE: lib/barbeque/message_handler.rb
type Barbeque (line 1) | module Barbeque
type MessageHandler (line 2) | module MessageHandler
class DuplicatedExecution (line 3) | class DuplicatedExecution < StandardError; end
FILE: lib/barbeque/message_handler/job_execution.rb
type Barbeque (line 5) | module Barbeque
type MessageHandler (line 6) | module MessageHandler
class JobExecution (line 7) | class JobExecution
method initialize (line 10) | def initialize(message:, message_queue:)
method run (line 15) | def run
method job_envs (line 30) | def job_envs
method job_definition (line 41) | def job_definition
method create_job_execution (line 48) | def create_job_execution
FILE: lib/barbeque/message_handler/job_retry.rb
type Barbeque (line 5) | module Barbeque
type MessageHandler (line 6) | module MessageHandler
class MessageNotFound (line 7) | class MessageNotFound < StandardError; end
class JobRetry (line 9) | class JobRetry
method initialize (line 12) | def initialize(message:, message_queue:)
method run (line 17) | def run
method job_execution (line 33) | def job_execution
method job_envs (line 37) | def job_envs
method create_job_retry (line 52) | def create_job_retry
FILE: lib/barbeque/message_handler/notification.rb
type Barbeque (line 3) | module Barbeque
type MessageHandler (line 4) | module MessageHandler
class Notification (line 5) | class Notification < JobExecution
method initialize (line 8) | def initialize(message:, message_queue:)
FILE: lib/barbeque/message_queue.rb
type Barbeque (line 5) | module Barbeque
class MessageQueue (line 6) | class MessageQueue
method initialize (line 9) | def initialize(job_queue)
method dequeue (line 17) | def dequeue
method delete_message (line 33) | def delete_message(message)
method stop! (line 40) | def stop!
method receive_message (line 46) | def receive_message
method client (line 58) | def client
FILE: lib/barbeque/retry_poller.rb
type Barbeque (line 4) | module Barbeque
class RetryPoller (line 5) | class RetryPoller
method initialize (line 6) | def initialize(job_queue)
method run (line 11) | def run
method stop (line 31) | def stop
method poll (line 37) | def poll(job_retry)
FILE: lib/barbeque/runner.rb
type Barbeque (line 7) | module Barbeque
class Runner (line 12) | class Runner
method initialize (line 13) | def initialize(job_queue)
method run (line 17) | def run
method stop (line 28) | def stop
method message_queue (line 34) | def message_queue
method keep_maximum_concurrent_executions (line 38) | def keep_maximum_concurrent_executions
FILE: lib/barbeque/slack_client.rb
type Barbeque (line 5) | module Barbeque
class SlackClient (line 6) | class SlackClient
method initialize (line 7) | def initialize(channel)
method notify_success (line 11) | def notify_success(message)
method notify_failure (line 21) | def notify_failure(message)
method post_slack (line 33) | def post_slack(payload)
method default_payload (line 40) | def default_payload
method endpoint_uri (line 44) | def endpoint_uri
FILE: lib/barbeque/slack_notifier.rb
type Barbeque (line 3) | module Barbeque
type SlackNotifier (line 4) | module SlackNotifier
function notify_job_execution (line 7) | def notify_job_execution(job_execution)
function notify_job_retry (line 31) | def notify_job_retry(job_retry)
function barbeque_host (line 56) | def barbeque_host
function job_execution_link (line 60) | def job_execution_link(job_execution)
function job_execution_url (line 64) | def job_execution_url(job_execution)
function job_retry_link (line 68) | def job_retry_link(job_retry)
function job_retry_url (line 72) | def job_retry_url(job_retry)
function should_notify_failure? (line 76) | def should_notify_failure?(job_execution_with_slack_notification)
FILE: lib/barbeque/version.rb
type Barbeque (line 1) | module Barbeque
FILE: lib/barbeque/worker.rb
type Barbeque (line 7) | module Barbeque
type Worker (line 8) | module Worker
class UnexpectedMessageType (line 11) | class UnexpectedMessageType < StandardError; end
function run (line 13) | def self.run(
function initialize (line 36) | def initialize
function run (line 51) | def run
function stop (line 63) | def stop
function execute_command (line 68) | def execute_command
FILE: spec/barbeque/execution_log_spec.rb
function make_s3_object (line 16) | def make_s3_object(content)
FILE: spec/barbeque/worker_spec.rb
function worker_id (line 18) | def worker_id
FILE: spec/dummy/app/controllers/application_controller.rb
class ApplicationController (line 1) | class ApplicationController < ActionController::Base
FILE: spec/dummy/app/helpers/application_helper.rb
type ApplicationHelper (line 1) | module ApplicationHelper
FILE: spec/dummy/config/application.rb
type Dummy (line 21) | module Dummy
class Application (line 22) | class Application < Rails::Application
FILE: tools/s3-log-migrator.rb
function safe_join (line 80) | def safe_join(thread)
FILE: vendor/assets/javascripts/adminlte.js
function Plugin (line 98) | function Plugin(option) {
function Plugin (line 277) | function Plugin(option) {
function Plugin (line 436) | function Plugin(option) {
function Plugin (line 505) | function Plugin(option) {
function Plugin (line 680) | function Plugin(option) {
function Plugin (line 786) | function Plugin(option) {
function Plugin (line 944) | function Plugin(option) {
function Plugin (line 1124) | function Plugin(option) {
FILE: vendor/assets/javascripts/bootstrap.js
function transitionEnd (line 34) | function transitionEnd() {
function removeElement (line 127) | function removeElement() {
function Plugin (line 143) | function Plugin(option) {
function Plugin (line 252) | function Plugin(option) {
function Plugin (line 478) | function Plugin(option) {
function getTargetFromTrigger (line 705) | function getTargetFromTrigger($trigger) {
function Plugin (line 717) | function Plugin(option) {
function getParent (line 784) | function getParent($this) {
function clearMenus (line 797) | function clearMenus(e) {
function Plugin (line 890) | function Plugin(option) {
function Plugin (line 1234) | function Plugin(option, _relatedTarget) {
function allowedAttribute (line 1361) | function allowedAttribute(attr, allowedAttributeList) {
function sanitizeHtml (line 1386) | function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
function complete (line 1757) | function complete() {
function Plugin (line 1936) | function Plugin(option) {
function Plugin (line 2060) | function Plugin(option) {
function ScrollSpy (line 2103) | function ScrollSpy(element, options) {
function Plugin (line 2223) | function Plugin(option) {
function next (line 2332) | function next() {
function Plugin (line 2378) | function Plugin(option) {
function Plugin (line 2537) | function Plugin(option) {
FILE: vendor/assets/javascripts/plotly-basic.js
function s (line 7) | function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&re...
function d3_documentElement (line 266) | function d3_documentElement(node) {
function d3_window (line 269) | function d3_window(node) {
function d3_ascending (line 303) | function d3_ascending(a, b) {
function d3_number (line 366) | function d3_number(x) {
function d3_numeric (line 369) | function d3_numeric(x) {
function d3_bisector (line 428) | function d3_bisector(compare) {
function d3_transposeLength (line 489) | function d3_transposeLength(d) {
function d3_range_integerScale (line 541) | function d3_range_integerScale(x) {
function d3_class (line 546) | function d3_class(ctor, properties) {
function d3_Map (line 568) | function d3_Map() {
function d3_map_escape (line 601) | function d3_map_escape(key) {
function d3_map_unescape (line 604) | function d3_map_unescape(key) {
function d3_map_has (line 607) | function d3_map_has(key) {
function d3_map_remove (line 610) | function d3_map_remove(key) {
function d3_map_keys (line 613) | function d3_map_keys() {
function d3_map_size (line 618) | function d3_map_size() {
function d3_map_empty (line 623) | function d3_map_empty() {
function map (line 629) | function map(mapType, array, depth) {
function entries (line 653) | function entries(map, depth) {
function d3_Set (line 695) | function d3_Set() {
function d3_identity (line 713) | function d3_identity(d) {
function d3_rebind (line 721) | function d3_rebind(target, source, method) {
function d3_vendorSymbol (line 727) | function d3_vendorSymbol(object, name) {
function d3_noop (line 736) | function d3_noop() {}
function d3_dispatch (line 742) | function d3_dispatch() {}
function d3_dispatch_event (line 757) | function d3_dispatch_event(dispatch) {
function d3_eventPreventDefault (line 780) | function d3_eventPreventDefault() {
function d3_eventSource (line 783) | function d3_eventSource() {
function d3_eventDispatch (line 788) | function d3_eventDispatch(target) {
function d3_selection (line 814) | function d3_selection(groups) {
function d3_selection_selector (line 857) | function d3_selection_selector(selector) {
function d3_selection_selectorAll (line 875) | function d3_selection_selectorAll(selector) {
function d3_selection_attr (line 911) | function d3_selection_attr(name, value) {
function d3_collapse (line 935) | function d3_collapse(s) {
function d3_selection_classedRe (line 955) | function d3_selection_classedRe(name) {
function d3_selection_classes (line 958) | function d3_selection_classes(name) {
function d3_selection_classed (line 961) | function d3_selection_classed(name, value) {
function d3_selection_classedName (line 974) | function d3_selection_classedName(name) {
function d3_selection_style (line 1003) | function d3_selection_style(name, value, priority) {
function d3_selection_property (line 1024) | function d3_selection_property(name, value) {
function d3_selection_creator (line 1063) | function d3_selection_creator(name) {
function d3_selectionRemove (line 1083) | function d3_selectionRemove() {
function bind (line 1098) | function bind(group, groupData) {
function d3_selection_dataNode (line 1168) | function d3_selection_dataNode(data) {
function d3_selection_filter (line 1190) | function d3_selection_filter(selector) {
function d3_selection_sortComparator (line 1211) | function d3_selection_sortComparator(comparator) {
function d3_selection_each (line 1222) | function d3_selection_each(groups, callback) {
function d3_selection_enter (line 1254) | function d3_selection_enter(selection) {
function d3_selection_enterInsertBefore (line 1287) | function d3_selection_enterInsertBefore(enter) {
function d3_selection_on (line 1332) | function d3_selection_on(type, listener, capture) {
function d3_selection_onListener (line 1371) | function d3_selection_onListener(listener, argumentz) {
function d3_selection_onFilter (line 1383) | function d3_selection_onFilter(listener, argumentz) {
function d3_event_dragSuppress (line 1393) | function d3_event_dragSuppress(node) {
function d3_mousePoint (line 1421) | function d3_mousePoint(container, e) {
function drag (line 1460) | function drag() {
function dragstart (line 1463) | function dragstart(id, position, subject, move, end) {
function d3_behavior_dragTouchId (line 1507) | function d3_behavior_dragTouchId() {
function d3_sgn (line 1519) | function d3_sgn(x) {
function d3_cross2d (line 1522) | function d3_cross2d(a, b, c) {
function d3_acos (line 1525) | function d3_acos(x) {
function d3_asin (line 1528) | function d3_asin(x) {
function d3_sinh (line 1531) | function d3_sinh(x) {
function d3_cosh (line 1534) | function d3_cosh(x) {
function d3_tanh (line 1537) | function d3_tanh(x) {
function d3_haversin (line 1540) | function d3_haversin(x) {
function zoom (line 1577) | function zoom(g) {
function location (line 1678) | function location(p) {
function point (line 1681) | function point(l) {
function scaleTo (line 1684) | function scaleTo(s) {
function translateTo (line 1687) | function translateTo(p, l) {
function zoomTo (line 1692) | function zoomTo(that, p, l, k) {
function rescale (line 1704) | function rescale() {
function zoomstarted (line 1712) | function zoomstarted(dispatch) {
function zoomed (line 1717) | function zoomed(dispatch) {
function zoomended (line 1725) | function zoomended(dispatch) {
function mousedowned (line 1730) | function mousedowned() {
function touchstarted (line 1745) | function touchstarted() {
function mousewheeled (line 1815) | function mousewheeled() {
function dblclicked (line 1828) | function dblclicked() {
function d3_color (line 1836) | function d3_color() {}
function d3_hsl (line 1841) | function d3_hsl(h, s, l) {
function d3_hsl_rgb (line 1856) | function d3_hsl_rgb(h, s, l) {
function d3_hcl (line 1876) | function d3_hcl(h, c, l) {
function d3_hcl_lab (line 1889) | function d3_hcl_lab(h, c, l) {
function d3_lab (line 1895) | function d3_lab(l, a, b) {
function d3_lab_rgb (line 1910) | function d3_lab_rgb(l, a, b) {
function d3_lab_hcl (line 1917) | function d3_lab_hcl(l, a, b) {
function d3_lab_xyz (line 1920) | function d3_lab_xyz(x) {
function d3_xyz_lab (line 1923) | function d3_xyz_lab(x) {
function d3_xyz_rgb (line 1926) | function d3_xyz_rgb(r) {
function d3_rgb (line 1930) | function d3_rgb(r, g, b) {
function d3_rgbNumber (line 1933) | function d3_rgbNumber(value) {
function d3_rgbString (line 1936) | function d3_rgbString(value) {
function d3_rgb_hex (line 1959) | function d3_rgb_hex(v) {
function d3_rgb_parse (line 1962) | function d3_rgb_parse(format, rgb, hsl) {
function d3_rgb_hsl (line 1998) | function d3_rgb_hsl(r, g, b) {
function d3_rgb_lab (line 2010) | function d3_rgb_lab(r, g, b) {
function d3_rgb_xyz (line 2017) | function d3_rgb_xyz(r) {
function d3_rgb_parseNumber (line 2020) | function d3_rgb_parseNumber(c) {
function d3_functor (line 2177) | function d3_functor(v) {
function d3_xhrType (line 2184) | function d3_xhrType(response) {
function d3_xhr (line 2191) | function d3_xhr(url, mimeType, response, callback) {
function d3_xhr_fixCallback (line 2266) | function d3_xhr_fixCallback(callback) {
function d3_xhrHasResponse (line 2271) | function d3_xhrHasResponse(request) {
function dsv (line 2277) | function dsv(url, row, callback) {
function response (line 2285) | function response(request) {
function typedResponse (line 2288) | function typedResponse(f) {
function token (line 2307) | function token() {
function formatRow (line 2369) | function formatRow(row) {
function formatValue (line 2372) | function formatValue(text) {
function d3_timer (line 2385) | function d3_timer(callback, delay, then) {
function d3_timer_step (line 2403) | function d3_timer_step() {
function d3_timer_mark (line 2420) | function d3_timer_mark() {
function d3_timer_sweep (line 2428) | function d3_timer_sweep() {
function d3_format_precision (line 2441) | function d3_format_precision(x, p) {
function d3_formatPrefix (line 2458) | function d3_formatPrefix(d, i) {
function d3_locale_numberFormat (line 2469) | function d3_locale_numberFormat(locale) {
function d3_format_typeDefault (line 2590) | function d3_format_typeDefault(x) {
function d3_date_utc (line 2594) | function d3_date_utc() {
function d3_time_interval (line 2660) | function d3_time_interval(local, step, number) {
function d3_time_interval_utc (line 2708) | function d3_time_interval_utc(method) {
function d3_locale_timeFormat (line 2768) | function d3_locale_timeFormat(locale) {
function d3_time_formatPad (line 2989) | function d3_time_formatPad(value, fill, width) {
function d3_time_formatRe (line 2993) | function d3_time_formatRe(names) {
function d3_time_formatLookup (line 2996) | function d3_time_formatLookup(names) {
function d3_time_parseWeekdayNumber (line 3001) | function d3_time_parseWeekdayNumber(date, string, i) {
function d3_time_parseWeekNumberSunday (line 3006) | function d3_time_parseWeekNumberSunday(date, string, i) {
function d3_time_parseWeekNumberMonday (line 3011) | function d3_time_parseWeekNumberMonday(date, string, i) {
function d3_time_parseFullYear (line 3016) | function d3_time_parseFullYear(date, string, i) {
function d3_time_parseYear (line 3021) | function d3_time_parseYear(date, string, i) {
function d3_time_parseZone (line 3026) | function d3_time_parseZone(date, string, i) {
function d3_time_expandYear (line 3030) | function d3_time_expandYear(d) {
function d3_time_parseMonthNumber (line 3033) | function d3_time_parseMonthNumber(date, string, i) {
function d3_time_parseDay (line 3038) | function d3_time_parseDay(date, string, i) {
function d3_time_parseDayOfYear (line 3043) | function d3_time_parseDayOfYear(date, string, i) {
function d3_time_parseHour24 (line 3048) | function d3_time_parseHour24(date, string, i) {
function d3_time_parseMinutes (line 3053) | function d3_time_parseMinutes(date, string, i) {
function d3_time_parseSeconds (line 3058) | function d3_time_parseSeconds(date, string, i) {
function d3_time_parseMilliseconds (line 3063) | function d3_time_parseMilliseconds(date, string, i) {
function d3_time_zone (line 3068) | function d3_time_zone(d) {
function d3_time_parseLiteralPercent (line 3072) | function d3_time_parseLiteralPercent(date, string, i) {
function d3_time_formatMulti (line 3077) | function d3_time_formatMulti(formats) {
function d3_adder (line 3108) | function d3_adder() {}
function d3_adderSum (line 3125) | function d3_adderSum(a, b, o) {
function d3_geo_streamGeometry (line 3136) | function d3_geo_streamGeometry(geometry, listener) {
function d3_geo_streamLine (line 3181) | function d3_geo_streamLine(coordinates, listener, closed) {
function d3_geo_streamPolygon (line 3187) | function d3_geo_streamPolygon(coordinates, listener) {
function d3_geo_areaRingStart (line 3216) | function d3_geo_areaRingStart() {
function d3_geo_cartesian (line 3234) | function d3_geo_cartesian(spherical) {
function d3_geo_cartesianDot (line 3238) | function d3_geo_cartesianDot(a, b) {
function d3_geo_cartesianCross (line 3241) | function d3_geo_cartesianCross(a, b) {
function d3_geo_cartesianAdd (line 3244) | function d3_geo_cartesianAdd(a, b) {
function d3_geo_cartesianScale (line 3249) | function d3_geo_cartesianScale(vector, k) {
function d3_geo_cartesianNormalize (line 3252) | function d3_geo_cartesianNormalize(d) {
function d3_geo_spherical (line 3258) | function d3_geo_spherical(cartesian) {
function d3_geo_sphericalEqual (line 3261) | function d3_geo_sphericalEqual(a, b) {
function point (line 3286) | function point(λ, φ) {
function linePoint (line 3291) | function linePoint(λ, φ) {
function lineStart (line 3331) | function lineStart() {
function lineEnd (line 3334) | function lineEnd() {
function ringPoint (line 3339) | function ringPoint(λ, φ) {
function ringStart (line 3347) | function ringStart() {
function ringEnd (line 3350) | function ringEnd() {
function angle (line 3357) | function angle(λ0, λ1) {
function compareRanges (line 3360) | function compareRanges(a, b) {
function withinRange (line 3363) | function withinRange(x, range) {
function d3_geo_centroidPoint (line 3417) | function d3_geo_centroidPoint(λ, φ) {
function d3_geo_centroidPointXYZ (line 3422) | function d3_geo_centroidPointXYZ(x, y, z) {
function d3_geo_centroidLineStart (line 3428) | function d3_geo_centroidLineStart() {
function d3_geo_centroidLineEnd (line 3449) | function d3_geo_centroidLineEnd() {
function d3_geo_centroidRingStart (line 3452) | function d3_geo_centroidRingStart() {
function d3_geo_compose (line 3482) | function d3_geo_compose(a, b) {
function d3_true (line 3491) | function d3_true() {
function d3_geo_clipPolygon (line 3494) | function d3_geo_clipPolygon(segments, compare, clipStartInside, interpol...
function d3_geo_clipPolygonLinkCircular (line 3553) | function d3_geo_clipPolygonLinkCircular(array) {
function d3_geo_clipPolygonIntersection (line 3564) | function d3_geo_clipPolygonIntersection(point, points, other, entry) {
function d3_geo_clip (line 3572) | function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
function d3_geo_clipSegmentLength1 (line 3664) | function d3_geo_clipSegmentLength1(segment) {
function d3_geo_clipBufferListener (line 3667) | function d3_geo_clipBufferListener() {
function d3_geo_clipSort (line 3688) | function d3_geo_clipSort(a, b) {
function d3_geo_clipAntimeridianLine (line 3692) | function d3_geo_clipAntimeridianLine(listener) {
function d3_geo_clipAntimeridianIntersect (line 3731) | function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) {
function d3_geo_clipAntimeridianInterpolate (line 3735) | function d3_geo_clipAntimeridianInterpolate(from, to, direction, listene...
function d3_geo_pointInPolygon (line 3758) | function d3_geo_pointInPolygon(point, polygon) {
function d3_geo_clipCircle (line 3787) | function d3_geo_clipCircle(radius) {
function d3_geom_clipLine (line 3883) | function d3_geom_clipLine(x0, y0, x1, y1) {
function d3_geo_clipExtent (line 3955) | function d3_geo_clipExtent(x0, y0, x1, y1) {
function d3_geo_conic (line 4089) | function d3_geo_conic(projectAt) {
function d3_geo_conicEqualArea (line 4097) | function d3_geo_conicEqualArea(φ0, φ1) {
function albersUsa (line 4124) | function albersUsa(coordinates) {
function d3_geo_pathAreaRingStart (line 4206) | function d3_geo_pathAreaRingStart() {
function d3_geo_pathBoundsPoint (line 4228) | function d3_geo_pathBoundsPoint(x, y) {
function d3_geo_pathBuffer (line 4234) | function d3_geo_pathBuffer() {
function d3_geo_pathBufferCircle (line 4279) | function d3_geo_pathBufferCircle(radius) {
function d3_geo_pathCentroidPoint (line 4295) | function d3_geo_pathCentroidPoint(x, y) {
function d3_geo_pathCentroidLineStart (line 4300) | function d3_geo_pathCentroidLineStart() {
function d3_geo_pathCentroidLineEnd (line 4314) | function d3_geo_pathCentroidLineEnd() {
function d3_geo_pathCentroidRingStart (line 4317) | function d3_geo_pathCentroidRingStart() {
function d3_geo_pathContext (line 4338) | function d3_geo_pathContext(context) {
function d3_geo_resample (line 4378) | function d3_geo_resample(project) {
function path (line 4458) | function path(object) {
function reset (line 4497) | function reset() {
function d3_geo_pathProjectStream (line 4503) | function d3_geo_pathProjectStream(project) {
function d3_geo_transform (line 4520) | function d3_geo_transform(stream) {
function d3_geo_transformPoint (line 4543) | function d3_geo_transformPoint(stream, point) {
function d3_geo_projection (line 4565) | function d3_geo_projection(project) {
function d3_geo_projectionMutator (line 4570) | function d3_geo_projectionMutator(projectAt) {
function d3_geo_projectionRadians (line 4642) | function d3_geo_projectionRadians(stream) {
function d3_geo_equirectangular (line 4647) | function d3_geo_equirectangular(λ, φ) {
function forward (line 4655) | function forward(coordinates) {
function d3_geo_identityRotation (line 4665) | function d3_geo_identityRotation(λ, φ) {
function d3_geo_rotation (line 4669) | function d3_geo_rotation(δλ, δφ, δγ) {
function d3_geo_forwardRotationλ (line 4672) | function d3_geo_forwardRotationλ(δλ) {
function d3_geo_rotationλ (line 4677) | function d3_geo_rotationλ(δλ) {
function d3_geo_rotationφγ (line 4682) | function d3_geo_rotationφγ(δφ, δγ) {
function circle (line 4696) | function circle() {
function d3_geo_circleInterpolate (line 4726) | function d3_geo_circleInterpolate(radius, precision) {
function d3_geo_circleAngle (line 4743) | function d3_geo_circleAngle(cr, point) {
function graticule (line 4756) | function graticule() {
function lines (line 4762) | function lines() {
function d3_geo_graticuleX (line 4828) | function d3_geo_graticuleX(y0, y1, dy) {
function d3_geo_graticuleY (line 4836) | function d3_geo_graticuleY(x0, x1, dx) {
function d3_source (line 4844) | function d3_source(d) {
function d3_target (line 4847) | function d3_target(d) {
function greatArc (line 4852) | function greatArc() {
function d3_geo_interpolate (line 4879) | function d3_geo_interpolate(x0, y0, x1, y1) {
function d3_geo_lengthLineStart (line 4904) | function d3_geo_lengthLineStart() {
function d3_geo_azimuthal (line 4919) | function d3_geo_azimuthal(scale, angle) {
function d3_geo_conicConformal (line 4945) | function d3_geo_conicConformal(φ0, φ1) {
function d3_geo_conicEquidistant (line 4968) | function d3_geo_conicEquidistant(φ0, φ1) {
function d3_geo_mercator (line 4990) | function d3_geo_mercator(λ, φ) {
function d3_geo_mercatorProjection (line 4996) | function d3_geo_mercatorProjection(project) {
function d3_geo_transverseMercator (line 5037) | function d3_geo_transverseMercator(λ, φ) {
function d3_geom_pointX (line 5055) | function d3_geom_pointX(d) {
function d3_geom_pointY (line 5058) | function d3_geom_pointY(d) {
function hull (line 5064) | function hull(data) {
function d3_geom_hullUpper (line 5086) | function d3_geom_hullUpper(points) {
function d3_geom_hullOrder (line 5094) | function d3_geom_hullOrder(a, b) {
function d3_geom_polygonInside (line 5148) | function d3_geom_polygonInside(p, a, b) {
function d3_geom_polygonIntersect (line 5151) | function d3_geom_polygonIntersect(c, d, a, b) {
function d3_geom_polygonClosed (line 5155) | function d3_geom_polygonClosed(coordinates) {
function d3_geom_voronoiBeach (line 5160) | function d3_geom_voronoiBeach() {
function d3_geom_voronoiCreateBeach (line 5164) | function d3_geom_voronoiCreateBeach(site) {
function d3_geom_voronoiDetachBeach (line 5169) | function d3_geom_voronoiDetachBeach(beach) {
function d3_geom_voronoiRemoveBeach (line 5175) | function d3_geom_voronoiRemoveBeach(beach) {
function d3_geom_voronoiAddBeach (line 5211) | function d3_geom_voronoiAddBeach(site) {
function d3_geom_voronoiLeftBreakPoint (line 5265) | function d3_geom_voronoiLeftBreakPoint(arc, directrix) {
function d3_geom_voronoiRightBreakPoint (line 5277) | function d3_geom_voronoiRightBreakPoint(arc, directrix) {
function d3_geom_voronoiCell (line 5283) | function d3_geom_voronoiCell(site) {
function d3_geom_voronoiCloseCells (line 5296) | function d3_geom_voronoiCloseCells(extent) {
function d3_geom_voronoiHalfEdgeOrder (line 5326) | function d3_geom_voronoiHalfEdgeOrder(a, b) {
function d3_geom_voronoiCircle (line 5329) | function d3_geom_voronoiCircle() {
function d3_geom_voronoiAttachCircle (line 5333) | function d3_geom_voronoiAttachCircle(arc) {
function d3_geom_voronoiDetachCircle (line 5366) | function d3_geom_voronoiDetachCircle(arc) {
function d3_geom_voronoiClipEdges (line 5376) | function d3_geom_voronoiClipEdges(extent) {
function d3_geom_voronoiConnectEdge (line 5386) | function d3_geom_voronoiConnectEdge(edge, extent) {
function d3_geom_voronoiEdge (line 5460) | function d3_geom_voronoiEdge(lSite, rSite) {
function d3_geom_voronoiCreateEdge (line 5465) | function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) {
function d3_geom_voronoiCreateBorderEdge (line 5474) | function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) {
function d3_geom_voronoiSetEdgeEnd (line 5481) | function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) {
function d3_geom_voronoiHalfEdge (line 5492) | function d3_geom_voronoiHalfEdge(edge, lSite, rSite) {
function d3_geom_voronoiRedBlackTree (line 5506) | function d3_geom_voronoiRedBlackTree() {
function d3_geom_voronoiRedBlackNode (line 5509) | function d3_geom_voronoiRedBlackNode(node) {
function d3_geom_voronoiRedBlackRotateLeft (line 5672) | function d3_geom_voronoiRedBlackRotateLeft(tree, node) {
function d3_geom_voronoiRedBlackRotateRight (line 5685) | function d3_geom_voronoiRedBlackRotateRight(tree, node) {
function d3_geom_voronoiRedBlackFirst (line 5698) | function d3_geom_voronoiRedBlackFirst(node) {
function d3_geom_voronoi (line 5702) | function d3_geom_voronoi(sites, bbox) {
function d3_geom_voronoiVertexOrder (line 5731) | function d3_geom_voronoiVertexOrder(a, b) {
function voronoi (line 5737) | function voronoi(data) {
function sites (line 5748) | function sites(data) {
function d3_geom_voronoiTriangleArea (line 5801) | function d3_geom_voronoiTriangleArea(a, b, c) {
function quadtree (line 5819) | function quadtree(data) {
function d3_geom_quadtreeCompatX (line 5914) | function d3_geom_quadtreeCompatX(d) {
function d3_geom_quadtreeCompatY (line 5917) | function d3_geom_quadtreeCompatY(d) {
function d3_geom_quadtreeNode (line 5920) | function d3_geom_quadtreeNode() {
function d3_geom_quadtreeVisit (line 5929) | function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
function d3_geom_quadtreeFind (line 5938) | function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) {
function d3_interpolateRgb (line 5975) | function d3_interpolateRgb(a, b) {
function d3_interpolateObject (line 5984) | function d3_interpolateObject(a, b) {
function d3_interpolateNumber (line 6004) | function d3_interpolateNumber(a, b) {
function d3_interpolateString (line 6011) | function d3_interpolateString(a, b) {
function d3_interpolate (line 6045) | function d3_interpolate(a, b) {
function d3_interpolateArray (line 6055) | function d3_interpolateArray(a, b) {
function d3_ease_clamp (line 6106) | function d3_ease_clamp(f) {
function d3_ease_reverse (line 6111) | function d3_ease_reverse(f) {
function d3_ease_reflect (line 6116) | function d3_ease_reflect(f) {
function d3_ease_quad (line 6121) | function d3_ease_quad(t) {
function d3_ease_cubic (line 6124) | function d3_ease_cubic(t) {
function d3_ease_cubicInOut (line 6127) | function d3_ease_cubicInOut(t) {
function d3_ease_poly (line 6133) | function d3_ease_poly(e) {
function d3_ease_sin (line 6138) | function d3_ease_sin(t) {
function d3_ease_exp (line 6141) | function d3_ease_exp(t) {
function d3_ease_circle (line 6144) | function d3_ease_circle(t) {
function d3_ease_elastic (line 6147) | function d3_ease_elastic(a, p) {
function d3_ease_back (line 6155) | function d3_ease_back(s) {
function d3_ease_bounce (line 6161) | function d3_ease_bounce(t) {
function d3_interpolateHcl (line 6165) | function d3_interpolateHcl(a, b) {
function d3_interpolateHsl (line 6176) | function d3_interpolateHsl(a, b) {
function d3_interpolateLab (line 6187) | function d3_interpolateLab(a, b) {
function d3_interpolateRound (line 6196) | function d3_interpolateRound(a, b) {
function d3_transform (line 6212) | function d3_transform(m) {
function d3_transformDot (line 6228) | function d3_transformDot(a, b) {
function d3_transformNormalize (line 6231) | function d3_transformNormalize(a) {
function d3_transformCombine (line 6239) | function d3_transformCombine(a, b, k) {
function d3_interpolateTransformPop (line 6253) | function d3_interpolateTransformPop(s) {
function d3_interpolateTranslate (line 6256) | function d3_interpolateTranslate(ta, tb, s, q) {
function d3_interpolateRotate (line 6270) | function d3_interpolateRotate(ra, rb, s, q) {
function d3_interpolateSkew (line 6281) | function d3_interpolateSkew(wa, wb, s, q) {
function d3_interpolateScale (line 6291) | function d3_interpolateScale(ka, kb, s, q) {
function d3_interpolateTransform (line 6305) | function d3_interpolateTransform(a, b) {
function d3_uninterpolateNumber (line 6319) | function d3_uninterpolateNumber(a, b) {
function d3_uninterpolateClamp (line 6325) | function d3_uninterpolateClamp(a, b) {
function d3_layout_bundlePath (line 6339) | function d3_layout_bundlePath(link) {
function d3_layout_bundleAncestors (line 6352) | function d3_layout_bundleAncestors(node) {
function d3_layout_bundleLeastCommonAncestor (line 6362) | function d3_layout_bundleLeastCommonAncestor(a, b) {
function relayout (line 6374) | function relayout() {
function resort (line 6440) | function resort() {
function repulse (line 6487) | function repulse(node) {
function position (line 6668) | function position(dimension, size) {
function dragmove (line 6697) | function dragmove(d) {
function d3_layout_forceDragstart (line 6703) | function d3_layout_forceDragstart(d) {
function d3_layout_forceDragend (line 6706) | function d3_layout_forceDragend(d) {
function d3_layout_forceMouseover (line 6709) | function d3_layout_forceMouseover(d) {
function d3_layout_forceMouseout (line 6713) | function d3_layout_forceMouseout(d) {
function d3_layout_forceAccumulate (line 6716) | function d3_layout_forceAccumulate(quad, alpha, charges) {
function hierarchy (line 6746) | function hierarchy(root) {
function d3_layout_hierarchyRebind (line 6802) | function d3_layout_hierarchyRebind(object, hierarchy) {
function d3_layout_hierarchyVisitBefore (line 6808) | function d3_layout_hierarchyVisitBefore(node, callback) {
function d3_layout_hierarchyVisitAfter (line 6818) | function d3_layout_hierarchyVisitAfter(node, callback) {
function d3_layout_hierarchyChildren (line 6831) | function d3_layout_hierarchyChildren(d) {
function d3_layout_hierarchyValue (line 6834) | function d3_layout_hierarchyValue(d) {
function d3_layout_hierarchySort (line 6837) | function d3_layout_hierarchySort(a, b) {
function d3_layout_hierarchyLinks (line 6840) | function d3_layout_hierarchyLinks(nodes) {
function position (line 6852) | function position(node, x, dx, dy) {
function depth (line 6867) | function depth(node) {
function partition (line 6875) | function partition(d, i) {
function pie (line 6889) | function pie(data) {
function stack (line 6939) | function stack(data, index) {
function d3_layout_stackX (line 6994) | function d3_layout_stackX(d) {
function d3_layout_stackY (line 6997) | function d3_layout_stackY(d) {
function d3_layout_stackOut (line 7000) | function d3_layout_stackOut(d, y0, y) {
function d3_layout_stackOrderDefault (line 7067) | function d3_layout_stackOrderDefault(data) {
function d3_layout_stackOffsetZero (line 7070) | function d3_layout_stackOffsetZero(data) {
function d3_layout_stackMaxIndex (line 7075) | function d3_layout_stackMaxIndex(array) {
function d3_layout_stackReduceSum (line 7085) | function d3_layout_stackReduceSum(d) {
function d3_layout_stackSum (line 7088) | function d3_layout_stackSum(p, d) {
function histogram (line 7093) | function histogram(data, i) {
function d3_layout_histogramBinSturges (line 7137) | function d3_layout_histogramBinSturges(range, values) {
function d3_layout_histogramBinFixed (line 7140) | function d3_layout_histogramBinFixed(range, n) {
function d3_layout_histogramRange (line 7145) | function d3_layout_histogramRange(values) {
function pack (line 7150) | function pack(d, i) {
function d3_layout_packSort (line 7189) | function d3_layout_packSort(a, b) {
function d3_layout_packInsert (line 7192) | function d3_layout_packInsert(a, b) {
function d3_layout_packSplice (line 7199) | function d3_layout_packSplice(a, b) {
function d3_layout_packIntersects (line 7203) | function d3_layout_packIntersects(a, b) {
function d3_layout_packSiblings (line 7207) | function d3_layout_packSiblings(node) {
function d3_layout_packLink (line 7271) | function d3_layout_packLink(node) {
function d3_layout_packUnlink (line 7274) | function d3_layout_packUnlink(node) {
function d3_layout_packTransform (line 7278) | function d3_layout_packTransform(node, x, y, k) {
function d3_layout_packPlace (line 7288) | function d3_layout_packPlace(a, b, c) {
function tree (line 7304) | function tree(d, i) {
function wrapTree (line 7323) | function wrapTree(root0) {
function firstWalk (line 7347) | function firstWalk(v) {
function secondWalk (line 7363) | function secondWalk(v) {
function apportion (line 7367) | function apportion(v, w, ancestor) {
function sizeNode (line 7397) | function sizeNode(node) {
function d3_layout_treeSeparation (line 7418) | function d3_layout_treeSeparation(a, b) {
function d3_layout_treeLeft (line 7421) | function d3_layout_treeLeft(v) {
function d3_layout_treeRight (line 7425) | function d3_layout_treeRight(v) {
function d3_layout_treeMove (line 7429) | function d3_layout_treeMove(wm, wp, shift) {
function d3_layout_treeShift (line 7437) | function d3_layout_treeShift(v) {
function d3_layout_treeAncestor (line 7446) | function d3_layout_treeAncestor(vim, v, ancestor) {
function cluster (line 7451) | function cluster(d, i) {
function d3_layout_clusterY (line 7491) | function d3_layout_clusterY(children) {
function d3_layout_clusterX (line 7496) | function d3_layout_clusterX(children) {
function d3_layout_clusterLeft (line 7501) | function d3_layout_clusterLeft(node) {
function d3_layout_clusterRight (line 7505) | function d3_layout_clusterRight(node) {
function scale (line 7511) | function scale(children, k) {
function squarify (line 7518) | function squarify(node) {
function stickify (line 7545) | function stickify(node) {
function worst (line 7562) | function worst(row, u) {
function position (line 7573) | function position(row, u, rect, flush) {
function treemap (line 7603) | function treemap(d) {
function padFunction (line 7620) | function padFunction(node) {
function padConstant (line 7624) | function padConstant(node) {
function d3_layout_treemapPadNull (line 7655) | function d3_layout_treemapPadNull(node) {
function d3_layout_treemapPad (line 7663) | function d3_layout_treemapPad(node, padding) {
function d3_scaleExtent (line 7715) | function d3_scaleExtent(domain) {
function d3_scaleRange (line 7719) | function d3_scaleRange(scale) {
function d3_scale_bilinear (line 7722) | function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
function d3_scale_nice (line 7728) | function d3_scale_nice(domain, nice) {
function d3_scale_niceStep (line 7738) | function d3_scale_niceStep(step) {
function d3_scale_polylinear (line 7752) | function d3_scale_polylinear(domain, range, uninterpolate, interpolate) {
function d3_scale_linear (line 7770) | function d3_scale_linear(domain, range, interpolate, clamp) {
function d3_scale_linearRebind (line 7822) | function d3_scale_linearRebind(scale, linear) {
function d3_scale_linearNice (line 7825) | function d3_scale_linearNice(domain, m) {
function d3_scale_linearTickRange (line 7830) | function d3_scale_linearTickRange(domain, m) {
function d3_scale_linearTicks (line 7839) | function d3_scale_linearTicks(domain, m) {
function d3_scale_linearTickFormat (line 7842) | function d3_scale_linearTickFormat(domain, m, format) {
function d3_scale_linearPrecision (line 7870) | function d3_scale_linearPrecision(value) {
function d3_scale_linearFormatPrecision (line 7873) | function d3_scale_linearFormatPrecision(type, range) {
function d3_scale_log (line 7880) | function d3_scale_log(linear, base, positive, domain) {
function d3_scale_pow (line 7953) | function d3_scale_pow(linear, exponent, domain) {
function d3_scale_powPow (line 7987) | function d3_scale_powPow(e) {
function d3_scale_ordinal (line 8001) | function d3_scale_ordinal(domain, ranger) {
function d3_scale_quantile (line 8109) | function d3_scale_quantile(domain, range) {
function d3_scale_quantize (line 8145) | function d3_scale_quantize(x0, x1, range) {
function d3_scale_threshold (line 8179) | function d3_scale_threshold(domain, range) {
function d3_scale_identity (line 8205) | function d3_scale_identity(domain) {
function d3_zero (line 8227) | function d3_zero() {
function arc (line 8232) | function arc() {
function circleSegment (line 8310) | function circleSegment(r1, cw) {
function d3_svg_arcInnerRadius (line 8355) | function d3_svg_arcInnerRadius(d) {
function d3_svg_arcOuterRadius (line 8358) | function d3_svg_arcOuterRadius(d) {
function d3_svg_arcStartAngle (line 8361) | function d3_svg_arcStartAngle(d) {
function d3_svg_arcEndAngle (line 8364) | function d3_svg_arcEndAngle(d) {
function d3_svg_arcPadAngle (line 8367) | function d3_svg_arcPadAngle(d) {
function d3_svg_arcSweep (line 8370) | function d3_svg_arcSweep(x0, y0, x1, y1) {
function d3_svg_arcCornerTangents (line 8373) | function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) {
function d3_svg_line (line 8378) | function d3_svg_line(projection) {
function d3_svg_lineLinear (line 8445) | function d3_svg_lineLinear(points) {
function d3_svg_lineLinearClosed (line 8448) | function d3_svg_lineLinearClosed(points) {
function d3_svg_lineStep (line 8451) | function d3_svg_lineStep(points) {
function d3_svg_lineStepBefore (line 8457) | function d3_svg_lineStepBefore(points) {
function d3_svg_lineStepAfter (line 8462) | function d3_svg_lineStepAfter(points) {
function d3_svg_lineCardinalOpen (line 8467) | function d3_svg_lineCardinalOpen(points, tension) {
function d3_svg_lineCardinalClosed (line 8470) | function d3_svg_lineCardinalClosed(points, tension) {
function d3_svg_lineCardinal (line 8474) | function d3_svg_lineCardinal(points, tension) {
function d3_svg_lineHermite (line 8477) | function d3_svg_lineHermite(points, tangents) {
function d3_svg_lineCardinalTangents (line 8504) | function d3_svg_lineCardinalTangents(points, tension) {
function d3_svg_lineBasis (line 8514) | function d3_svg_lineBasis(points) {
function d3_svg_lineBasisOpen (line 8530) | function d3_svg_lineBasisOpen(points) {
function d3_svg_lineBasisClosed (line 8550) | function d3_svg_lineBasisClosed(points) {
function d3_svg_lineBundle (line 8569) | function d3_svg_lineBundle(points, tension) {
function d3_svg_lineDot4 (line 8582) | function d3_svg_lineDot4(a, b) {
function d3_svg_lineBasisBezier (line 8586) | function d3_svg_lineBasisBezier(path, x, y) {
function d3_svg_lineSlope (line 8589) | function d3_svg_lineSlope(p0, p1) {
function d3_svg_lineFiniteDifferences (line 8592) | function d3_svg_lineFiniteDifferences(points) {
function d3_svg_lineMonotoneTangents (line 8600) | function d3_svg_lineMonotoneTangents(points) {
function d3_svg_lineMonotone (line 8624) | function d3_svg_lineMonotone(points) {
function d3_svg_lineRadial (line 8633) | function d3_svg_lineRadial(points) {
function d3_svg_area (line 8644) | function d3_svg_area(projection) {
function chord (line 8734) | function chord(d, i) {
function subgroup (line 8738) | function subgroup(self, f, d, i) {
function equals (line 8748) | function equals(a, b) {
function arc (line 8751) | function arc(r, p, a) {
function curve (line 8754) | function curve(r0, p0, r1, p1) {
function d3_svg_chordRadius (line 8784) | function d3_svg_chordRadius(d) {
function diagonal (line 8789) | function diagonal(d, i) {
function d3_svg_diagonalProjection (line 8817) | function d3_svg_diagonalProjection(d) {
function d3_svg_diagonalRadialProjection (line 8827) | function d3_svg_diagonalRadialProjection(projection) {
function symbol (line 8835) | function symbol(d, i) {
function d3_svg_symbolSize (line 8850) | function d3_svg_symbolSize() {
function d3_svg_symbolType (line 8853) | function d3_svg_symbolType() {
function d3_svg_symbolCircle (line 8856) | function d3_svg_symbolCircle(size) {
function d3_selection_interruptNS (line 8905) | function d3_selection_interruptNS(ns) {
function d3_transition (line 8917) | function d3_transition(groups, ns, id) {
function d3_transition_tween (line 8989) | function d3_transition_tween(groups, name, value, tween) {
function attrNull (line 9003) | function attrNull() {
function attrNullNS (line 9006) | function attrNullNS() {
function attrTween (line 9009) | function attrTween(b) {
function attrTweenNS (line 9017) | function attrTweenNS(b) {
function attrTween (line 9029) | function attrTween(d, i) {
function attrTweenNS (line 9035) | function attrTweenNS(d, i) {
function styleNull (line 9053) | function styleNull() {
function styleString (line 9056) | function styleString(b) {
function styleTween (line 9068) | function styleTween(d, i) {
function d3_transition_text (line 9079) | function d3_transition_text(b) {
function d3_transitionNamespace (line 9159) | function d3_transitionNamespace(name) {
function d3_transitionNode (line 9162) | function d3_transitionNode(node, i, ns, id, inherit) {
function axis (line 9239) | function axis(g) {
function d3_svg_axisX (line 9334) | function d3_svg_axisX(selection, x0, x1) {
function d3_svg_axisY (line 9340) | function d3_svg_axisY(selection, y0, y1) {
function brush (line 9348) | function brush(g) {
function redraw (line 9434) | function redraw(g) {
function redrawX (line 9439) | function redrawX(g) {
function redrawY (line 9443) | function redrawY(g) {
function brushstart (line 9447) | function brushstart() {
function d3_time_formatIsoNative (line 9640) | function d3_time_formatIsoNative(date) {
function d3_time_scale (line 9687) | function d3_time_scale(linear, methods, format) {
function d3_time_scaleDate (line 9737) | function d3_time_scaleDate(t) {
function d3_json (line 9796) | function d3_json(request) {
function d3_html (line 9802) | function d3_html(request) {
function objectOrFunction (line 9828) | function objectOrFunction(x) {
function isFunction (line 9832) | function isFunction(x) {
function setScheduler (line 9867) | function setScheduler(scheduleFn) {
function setAsap (line 9871) | function setAsap(asapFn) {
function useNextTick (line 9884) | function useNextTick() {
function useVertxTimer (line 9893) | function useVertxTimer() {
function useMutationObserver (line 9899) | function useMutationObserver() {
function useMessageChannel (line 9911) | function useMessageChannel() {
function useSetTimeout (line 9919) | function useSetTimeout() {
function flush (line 9929) | function flush() {
function attemptVertx (line 9943) | function attemptVertx() {
function then (line 9968) | function then(onFulfillment, onRejection) {
function resolve (line 10026) | function resolve(object) {
function noop (line 10041) | function noop() {}
function selfFulfillment (line 10049) | function selfFulfillment() {
function cannotReturnOwn (line 10053) | function cannotReturnOwn() {
function getThen (line 10057) | function getThen(promise) {
function tryThen (line 10066) | function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
function handleForeignThenable (line 10074) | function handleForeignThenable(promise, thenable, then) {
function handleOwnThenable (line 10103) | function handleOwnThenable(promise, thenable) {
function handleMaybeThenable (line 10117) | function handleMaybeThenable(promise, maybeThenable, then$$) {
function _resolve (line 10133) | function _resolve(promise, value) {
function publishRejection (line 10143) | function publishRejection(promise) {
function fulfill (line 10151) | function fulfill(promise, value) {
function _reject (line 10164) | function _reject(promise, reason) {
function subscribe (line 10174) | function subscribe(parent, child, onFulfillment, onRejection) {
function publish (line 10189) | function publish(promise) {
function ErrorObject (line 10215) | function ErrorObject() {
function tryCatch (line 10221) | function tryCatch(callback, detail) {
function invokeCallback (line 10230) | function invokeCallback(settled, promise, callback, detail) {
function initializePromise (line 10270) | function initializePromise(promise, resolver) {
function nextId (line 10283) | function nextId() {
function makePromise (line 10287) | function makePromise(promise) {
function Enumerator (line 10294) | function Enumerator(Constructor, input) {
function validationError (line 10323) | function validationError() {
function all (line 10437) | function all(entries) {
function race (line 10506) | function race(entries) {
function reject (line 10558) | function reject(reason) {
function needsResolver (line 10566) | function needsResolver() {
function needsNew (line 10570) | function needsNew() {
function Promise (line 10677) | function Promise(resolver) {
function polyfill (line 10926) | function polyfill() {
function EventEmitter (line 10991) | function EventEmitter() {
function g (line 11129) | function g() {
function isFunction (line 11257) | function isFunction(arg) {
function isNumber (line 11261) | function isNumber(arg) {
function isObject (line 11265) | function isObject(arg) {
function isUndefined (line 11269) | function isUndefined(arg) {
function allBlankCharCodes (line 11302) | function allBlankCharCodes(str){
function fromQuat (line 11340) | function fromQuat(out, q) {
function mouseEventOffset (line 11401) | function mouseEventOffset (ev, target, out) {
function getBoundingClientOffset (line 11414) | function getBoundingClientOffset (element) {
function defaultSetTimout (line 11436) | function defaultSetTimout() {
function defaultClearTimeout (line 11439) | function defaultClearTimeout () {
function runTimeout (line 11462) | function runTimeout(fun) {
function runClearTimeout (line 11487) | function runClearTimeout(marker) {
function cleanUpNextTick (line 11519) | function cleanUpNextTick() {
function drainQueue (line 11534) | function drainQueue() {
function Item (line 11572) | function Item(fun, array) {
function noop (line 11586) | function noop() {}
function tinycolor (line 11625) | function tinycolor (color, opts) {
function inputToRGB (line 11915) | function inputToRGB(color) {
function rgbToRgb (line 11979) | function rgbToRgb(r, g, b){
function rgbToHsl (line 11991) | function rgbToHsl(r, g, b) {
function hslToRgb (line 12022) | function hslToRgb(h, s, l) {
function rgbToHsv (line 12056) | function rgbToHsv(r, g, b) {
function hsvToRgb (line 12086) | function hsvToRgb(h, s, v) {
function rgbToHex (line 12109) | function rgbToHex(r, g, b, allow3Char) {
function rgbaToHex (line 12129) | function rgbaToHex(r, g, b, a, allow4Char) {
function rgbaToArgbHex (line 12149) | function rgbaToArgbHex(r, g, b, a) {
function desaturate (line 12182) | function desaturate(color, amount) {
function saturate (line 12190) | function saturate(color, amount) {
function greyscale (line 12198) | function greyscale(color) {
function lighten (line 12202) | function lighten (color, amount) {
function brighten (line 12210) | function brighten(color, amount) {
function darken (line 12219) | function darken (color, amount) {
function spin (line 12229) | function spin(color, amount) {
function complement (line 12241) | function complement(color) {
function triad (line 12247) | function triad(color) {
function tetrad (line 12257) | function tetrad(color) {
function splitcomplement (line 12268) | function splitcomplement(color) {
function analogous (line 12278) | function analogous(color, results, slices) {
function monochromatic (line 12293) | function monochromatic(color, results) {
function flip (line 12575) | function flip(o) {
function boundAlpha (line 12586) | function boundAlpha(a) {
function bound01 (line 12597) | function bound01(n, max) {
function clamp01 (line 12618) | function clamp01(val) {
function parseIntFromHex (line 12623) | function parseIntFromHex(val) {
function isOnePointZero (line 12629) | function isOnePointZero(n) {
function isPercentage (line 12634) | function isPercentage(n) {
function pad2 (line 12639) | function pad2(c) {
function convertToPercentage (line 12644) | function convertToPercentage(n) {
function convertDecimalToHex (line 12653) | function convertDecimalToHex(d) {
function convertHexToDecimal (line 12657) | function convertHexToDecimal(h) {
function isValidCSSUnit (line 12696) | function isValidCSSUnit(color) {
function stringInputToObject (line 12703) | function stringInputToObject(color) {
function validateWCAG2Parms (line 12776) | function validateWCAG2Parms(parms) {
function coerce (line 12829) | function coerce(attr, dflt) {
function annAutorange (line 13290) | function annAutorange(gd) {
function hasClickToShow (line 13382) | function hasClickToShow(gd, hoverData) {
function onClick (line 13397) | function onClick(gd, hoverData) {
function getToggleSets (line 13431) | function getToggleSets(gd, hoverData) {
function clickData2r (line 13482) | function clickData2r(d, ax) {
function convert (line 13595) | function convert(attr) {
function draw (line 13688) | function draw(gd) {
function drawOne (line 13707) | function drawOne(gd, index) {
function drawRaw (line 13727) | function drawRaw(gd, options, index, subplotId, xa, ya) {
function hideLine (line 14461) | function hideLine() { el3.style('stroke-dasharray', '0px,100px'); }
function drawhead (line 14463) | function drawhead(p, rot) {
function mockAnnAxes (line 14630) | function mockAnnAxes(ann, scene) {
function handleAnnotationDefaults (line 14693) | function handleAnnotationDefaults(annIn, annOut, sceneLayout, opts, item...
function cleanOne (line 15002) | function cleanOne(val) {
function coerce (line 15217) | function coerce(attr, dflt) {
function component (line 15319) | function component() {
function getTrace (line 15857) | function getTrace() {
function parseScale (line 16260) | function parseScale() {
function colorArray2rbga (line 16502) | function colorArray2rbga(colorArray) {
function onStart (line 16793) | function onStart(e) {
function onMove (line 16836) | function onMove(e) {
function onDone (line 16854) | function onDone(e) {
function coverSlip (line 16912) | function coverSlip() {
function finishDrag (line 16932) | function finishDrag(gd) {
function pointerOffset (line 16937) | function pointerOffset(e) {
function singlePointStyle (line 17280) | function singlePointStyle(d, sel, trace, markerScale, lineScale, marker,...
function makeTangent (line 17557) | function makeTangent(prevpt, thispt, nextpt, smoothness) {
function nodeHash (line 17782) | function nodeHash(node) {
function calcOneAxis (line 18557) | function calcOneAxis(calcTrace, trace, axis, coord) {
function makeComputeErrorValue (line 18661) | function makeComputeErrorValue(type, value) {
function coerce (line 18703) | function coerce(attr, dflt) {
function errorCoords (line 18952) | function errorCoords(d, xa, ya) {
function makeCoerceHoverInfo (line 19082) | function makeCoerceHoverInfo(trace) {
function paste (line 19112) | function paste(traceAttr, cd, cdAttr, fn) {
function emitClick (line 19145) | function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata, e...
function coerce (line 19206) | function coerce(attr, dflt) {
function quadrature (line 19293) | function quadrature(dx, dy) {
function _hover (line 19507) | function _hover(gd, evt, subplot, noHoverEvent) {
function createHoverText (line 19863) | function createHoverText(hoverData, opts, gd) {
function hoverAvoidOverlaps (line 20168) | function hoverAvoidOverlaps(hoverData, ax) {
function alignHoverText (line 20323) | function alignHoverText(hoverLabels, rotateLabels) {
function cleanPoint (line 20377) | function cleanPoint(d, hovermode) {
function createSpikelines (line 20487) | function createSpikelines(hoverData, opts) {
function hoverChanged (line 20620) | function hoverChanged(gd, evt, oldhoverdata) {
function loneUnhover (line 20709) | function loneUnhover(containerOrSelection) {
function castHoverOption (line 20722) | function castHoverOption(trace, ptNumber, attr) {
function castHoverinfo (line 20726) | function castHoverinfo(trace, fullLayout, ptNumber) {
function coerce (line 20809) | function coerce(attr, dflt) {
function isHoriz (line 20835) | function isHoriz(fullData) {
function coerce (line 20866) | function coerce(attr, dflt) {
function imageDefaults (line 21106) | function imageDefaults(imageIn, imageOut, fullLayout) {
function setImage (line 21213) | function setImage(d) {
function applyAttributes (line 21262) | function applyAttributes(d) {
function coerce (line 21593) | function coerce(attr, dflt) {
function scrollHandler (line 21932) | function scrollHandler(scrollBarY, scrollBoxY) {
function drawTexts (line 21998) | function drawTexts(g, gd) {
function setupTraceToggle (line 22052) | function setupTraceToggle(g, gd) {
function handleClick (line 22098) | function handleClick(g, gd, numClicks) {
function computeTextDimensions (line 22189) | function computeTextDimensions(g, gd) {
function computeLegendDimensions (line 22233) | function computeLegendDimensions(gd, groups, traces) {
function expandMargin (line 22373) | function expandMargin(gd) {
function expandHorizontalMargin (line 22404) | function expandHorizontalMargin(gd) {
function addOneItem (line 22452) | function addOneItem(legendGroup, legendItem) {
function styleLines (line 22649) | function styleLines(d) {
function stylePoints (line 22674) | function stylePoints(d) {
function styleBars (line 22758) | function styleBars(d) {
function styleBoxes (line 22783) | function styleBoxes(d) {
function stylePies (line 22806) | function stylePies(d) {
function handleCartesian (line 22995) | function handleCartesian(gd, ev) {
function handleDrag3d (line 23113) | function handleDrag3d(gd, ev) {
function handleCamera3d (line 23146) | function handleCamera3d(gd, ev) {
function handleHover3d (line 23180) | function handleHover3d(gd, ev) {
function handleGeo (line 23268) | function handleGeo(gd, ev) {
function toggleHover (line 23310) | function toggleHover(gd) {
function setSpikelineVisibility (line 23382) | function setSpikelineVisibility(gd) {
function getButtonGroups (line 23511) | function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) {
function areAllAxesFixed (line 23596) | function areAllAxesFixed(fullLayout) {
function isSelectable (line 23612) | function isSelectable(fullData) {
function appendButtonsToGroups (line 23636) | function appendButtonsToGroups(groups, buttons) {
function fillCustomButton (line 23650) | function fillCustomButton(customButtons) {
function ModeBar (line 23700) | function ModeBar(opts) {
function createModeBar (line 23948) | function createModeBar(gd, buttons) {
function coerce (line 24151) | function coerce(attr, dflt) {
function buttonsDefaults (line 24176) | function buttonsDefaults(containerIn, containerOut, calendar) {
function getPosDflt (line 24213) | function getPosDflt(containerOut, layout, counterAxes) {
function makeSelectorData (line 24320) | function makeSelectorData(gd) {
function selectorKeyFunc (line 24335) | function selectorKeyFunc(d) {
function isActive (line 24339) | function isActive(axisLayout, opts, update) {
function drawButtonRect (line 24353) | function drawButtonRect(button, selectorLayout, d) {
function getFillColor (line 24372) | function getFillColor(selectorLayout, d) {
function drawButtonText (line 24378) | function drawButtonText(button, selectorLayout, d, gd) {
function getLabel (line 24397) | function getLabel(opts) {
function reposition (line 24405) | function reposition(gd, buttons, opts, axName) {
function getXRange (line 24525) | function getXRange(axisLayout, buttonLayout) {
function coerce (line 24756) | function coerce(attr, dflt) {
function keyFunction (line 24837) | function keyFunction(axisOpts) {
function makeRangeSliderData (line 24942) | function makeRangeSliderData(fullLayout) {
function setupDragElement (line 24958) | function setupDragElement(rangeSlider, gd, axisOpts, opts) {
function setDataRange (line 25027) | function setDataRange(rangeSlider, gd, axisOpts, opts) {
function setPixelRange (line 25041) | function setPixelRange(rangeSlider, gd, axisOpts, opts) {
function drawBg (line 25080) | function drawBg(rangeSlider, gd, axisOpts, opts) {
function addClipPath (line 25109) | function addClipPath(rangeSlider, gd, axisOpts, opts) {
function drawRangePlot (line 25126) | function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
function filterRangePlotCalcData (line 25194) | function filterRangePlotCalcData(calcData, subplotId) {
function drawMasks (line 25209) | function drawMasks(rangeSlider, gd, axisOpts, opts) {
function drawSlideBox (line 25235) | function drawSlideBox(rangeSlider, gd, axisOpts, opts) {
function drawGrabbers (line 25253) | function drawGrabbers(rangeSlider, gd, axisOpts, opts) {
function clearPushMargins (line 25325) | function clearPushMargins(gd) {
function shapeBounds (line 25507) | function shapeBounds(ax, v0, v1, path, paramsToUse) {
function draw (line 25667) | function draw(gd) {
function drawOne (line 25685) | function drawOne(gd, index) {
function setupDragElement (line 25749) | function setupDragElement(gd, shapePath, shapeOptions, index) {
function getPathString (line 25903) | function getPathString(gd, options) {
function convertPath (line 25955) | function convertPath(pathIn, x2p, y2p) {
function movePath (line 25982) | function movePath(pathIn, moveX, moveY) {
function coerce (line 26135) | function coerce(attr, dflt) {
function sliderDefaults (line 26575) | function sliderDefaults(sliderIn, sliderOut, layoutOut) {
function stepsDefaults (line 26629) | function stepsDefaults(sliderIn, sliderOut) {
function makeSliderData (line 26754) | function makeSliderData(fullLayout, gd) {
function keyFunction (line 26769) | function keyFunction(opts) {
function findDimensions (line 26774) | function findDimensions(gd, sliderOpts) {
function drawSlider (line 26894) | function drawSlider(gd, sliderGroup, sliderOpts) {
function drawCurrentValue (line 26921) | function drawCurrentValue(sliderGroup, sliderOpts, valueOverride) {
function drawGrip (line 26980) | function drawGrip(sliderGroup, gd, sliderOpts) {
function drawLabel (line 27000) | function drawLabel(item, data, sliderOpts) {
function drawLabelGroup (line 27019) | function drawLabelGroup(sliderGroup, sliderOpts) {
function handleInput (line 27053) | function handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, do...
function setActive (line 27061) | function setActive(gd, sliderGroup, sliderOpts, index, doCallback, doTra...
function attachGripEvents (line 27100) | function attachGripEvents(item, gd, sliderGroup) {
function drawTicks (line 27148) | function drawTicks(sliderGroup, sliderOpts) {
function computeLabelSteps (line 27178) | function computeLabelSteps(sliderOpts) {
function setGripPosition (line 27191) | function setGripPosition(sliderGroup, sliderOpts, position, doTransition) {
function normalizedValueToPosition (line 27213) | function normalizedValueToPosition(sliderOpts, normalizedPosition) {
function positionToNormalizedValue (line 27219) | function positionToNormalizedValue(sliderOpts, position) {
function drawTouchRect (line 27223) | function drawTouchRect(sliderGroup, gd, sliderOpts) {
function drawRail (line 27242) | function drawRail(sliderGroup, sliderOpts) {
function clearPushMargins (line 27268) | function clearPushMargins(gd) {
function titleLayout (line 27416) | function titleLayout(titleEl) {
function drawTitle (line 27420) | function drawTitle(titleEl) {
function scootTitle (line 27439) | function scootTitle(titleElIn) {
function setPlaceholder (line 27508) | function setPlaceholder() {
function menuDefaults (line 27808) | function menuDefaults(menuIn, menuOut, layoutOut) {
function buttonsDefaults (line 27843) | function buttonsDefaults(menuIn, menuOut) {
function makeMenuData (line 28012) | function makeMenuData(fullLayout) {
function keyFunction (line 28033) | function keyFunction(menuOpts) {
function isFolded (line 28037) | function isFolded(gButton) {
function isActive (line 28041) | function isActive(gButton, menuOpts) {
function setActive (line 28045) | function setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox...
function drawHeader (line 28064) | function drawHeader(gd, gHeader, gButton, scrollBox, menuOpts) {
function drawButtons (line 28128) | function drawButtons(gd, gHeader, gButton, scrollBox, menuOpts) {
function drawScrollBox (line 28250) | function drawScrollBox(gd, gHeader, gButton, scrollBox, menuOpts, positi...
function hideScrollBox (line 28288) | function hideScrollBox(scrollBox) {
function drawItem (line 28313) | function drawItem(item, menuOpts, itemOpts, gd) {
function drawItemRect (line 28318) | function drawItemRect(item, menuOpts) {
function drawItemText (line 28335) | function drawItemText(item, menuOpts, itemOpts, gd) {
function styleButtons (line 28352) | function styleButtons(buttons, menuOpts) {
function styleOnMouseOver (line 28365) | function styleOnMouseOver(item) {
function styleOnMouseOut (line 28370) | function styleOnMouseOut(item, menuOpts) {
function findDimensions (line 28376) | function findDimensions(gd, menuOpts) {
function setItemPosition (line 28503) | function setItemPosition(item, menuOpts, posOpts, overrideOpts) {
function removeAllButtons (line 28538) | function removeAllButtons(gButton) {
function clearPushMargins (line 28542) | function clearPushMargins(gd) {
function ScrollBox (line 28585) | function ScrollBox(gd, container, id) {
function isWorldCalendar (line 29758) | function isWorldCalendar(calendar) {
function lpad (line 29947) | function lpad(val, digits) {
function includeTime (line 30037) | function includeTime(dateStr, h, m, s, msec10) {
function modDateFormat (line 30091) | function modDateFormat(fmt, x, calendar) {
function formatTime (line 30121) | function formatTime(x, tr) {
function yearFormatWorld (line 30162) | function yearFormatWorld(cDate) { return cDate.formatDate('yyyy'); }
function monthFormatWorld (line 30163) | function monthFormatWorld(cDate) { return cDate.formatDate('M yyyy'); }
function dayFormatWorld (line 30164) | function dayFormatWorld(cDate) { return cDate.formatDate('M d'); }
function yearMonthDayFormatWorld (line 30165) | function yearMonthDayFormatWorld(cDate) { return cDate.formatDate('M d, ...
function primitivesLoopSplice (line 30559) | function primitivesLoopSplice(source, target) {
function _extend (line 30606) | function _extend(inputs, isDeep, keepAllKeys, noArrayCopies) {
function segmentsIntersect (line 30758) | function segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
function perpDistance2 (line 30808) | function perpDistance2(xab, yab, l2_ab, xac, yac) {
function getDistToPlot (line 30900) | function getDistToPlot(len) {
function continueAsync (line 31260) | function continueAsync() {
function apply (line 31800) | function apply(f, args) {
function npGet (line 32020) | function npGet(cont, parts) {
function isDeletable (line 32081) | function isDeletable(val, propStr) {
function npSet (line 32099) | function npSet(cont, parts, propStr) {
function joinPropStr (line 32145) | function joinPropStr(propStr, newPart) {
function setArrayAll (line 32154) | function setArrayAll(containerArray, innerParts, val, propStr) {
function checkNewContainer (line 32183) | function checkNewContainer(container, part, nextPart, toDelete) {
function pruneContainers (line 32193) | function pruneContainers(containerLevels) {
function emptyObj (line 32228) | function emptyObj(obj) {
function badContainer (line 32235) | function badContainer(container, propStr, propParts) {
function killNote (line 32303) | function killNote(transition) {
function rectContains (line 32465) | function rectContains(pt, omitFirstEdge) {
function contains (line 32478) | function contains(pt, omitFirstEdge) {
function addPt (line 32603) | function addPt(pt) {
function copyArgArray (line 32694) | function copyArgArray(gd, args) {
function lessThan (line 32993) | function lessThan(a, b) { return a < b; }
function lessOrEqual (line 32994) | function lessOrEqual(a, b) { return a <= b; }
function greaterThan (line 32995) | function greaterThan(a, b) { return a > b; }
function greaterOrEqual (line 32996) | function greaterOrEqual(a, b) { return a >= b; }
function getSize (line 33192) | function getSize(_selection, _dimension) {
function showText (line 33223) | function showText() {
function cleanEscapesForTex (line 33330) | function cleanEscapesForTex(s) {
function texToSVG (line 33335) | function texToSVG(_texString, _config, _callback) {
function getQuotedMatch (line 33434) | function getQuotedMatch(_str, re) {
function replaceFromMapObject (line 33448) | function replaceFromMapObject(_str, list) {
function convertEntities (line 33459) | function convertEntities(_str) {
function buildSVGText (line 33473) | function buildSVGText(containerNode, str) {
function setOrGet (line 33656) | function setOrGet(attr, val) {
function alignHTMLWith (line 33677) | function alignHTMLWith(_base, container, options) {
function handleClick (line 33738) | function handleClick() {
function selectElementContents (line 33751) | function selectElementContents(_el) {
function appendEditable (line 33761) | function appendEditable() {
function cleanAxRef (line 34185) | function cleanAxRef(container, attr) {
function cleanTextPosition (line 34379) | function cleanTextPosition(textposition) {
function emptyContainer (line 34391) | function emptyContainer(outer, innerStr) {
function getParent (line 34493) | function getParent(attr) {
function addFrames (line 34816) | function addFrames() {
function drawFramework (line 34919) | function drawFramework() {
function marginPushers (line 34934) | function marginPushers() {
function marginPushersAgain (line 34957) | function marginPushersAgain() {
function positionAndAutorange (line 34966) | function positionAndAutorange() {
function doAutoRangeAndConstraints (line 35003) | function doAutoRangeAndConstraints() {
function drawAxes (line 35022) | function drawAxes() {
function drawData (line 35027) | function drawData() {
function finalDraw (line 35089) | function finalDraw() {
function opaqueSetBackground (line 35126) | function opaqueSetBackground(gd, bgColor) {
function setPlotContext (line 35131) | function setPlotContext(gd, config) {
function plotPolar (line 35197) | function plotPolar(gd, data, layout) {
function positivifyIndices (line 35333) | function positivifyIndices(indices, maxIndex) {
function assertIndexArray (line 35359) | function assertIndexArray(gd, indices, arrayName) {
function checkMoveTracesArgs (line 35392) | function checkMoveTracesArgs(gd, currentIndices, newIndices) {
function checkAddTracesArgs (line 35428) | function checkAddTracesArgs(gd, traces, newIndices) {
function assertExtendTracesArgs (line 35475) | function assertExtendTracesArgs(gd, update, indices, maxPoints) {
function getExtendProperties (line 35523) | function getExtendProperties(gd, update, indices, maxPoints) {
function spliceTraces (line 35599) | function spliceTraces(gd, update, indices, maxPoints, lengthenArray, spl...
function _restyle (line 36027) | function _restyle(gd, aobj, _traces) {
function _relayout (line 36613) | function _relayout(gd, aobj) {
function getTransitionOpts (line 37188) | function getTransitionOpts(i) {
function getFrameOpts (line 37200) | function getFrameOpts(i) {
function callbackOnNthTime (line 37217) | function callbackOnNthTime(cb, n) {
function discardExistingFrames (line 37227) | function discardExistingFrames() {
function queueFrames (line 37242) | function queueFrames(frameList) {
function stopAnimationLoop (line 37301) | function stopAnimationLoop() {
function nextFrame (line 37309) | function nextFrame() {
function beginAnimationLoop (line 37358) | function beginAnimationLoop() {
function setTransitionConfig (line 37385) | function setTransitionConfig(frame) {
function makePlotFramework (line 37720) | function makePlotFramework(gd) {
function defaultSetBackground (line 37960) | function defaultSetBackground(gd, bgColor) {
function callback (line 38119) | function callback(attr, attrName, attrs, level) {
function toAttrString (line 38144) | function toAttrString(stack) {
function getTraceAttributes (line 38182) | function getTraceAttributes(type) {
function getLayoutAttributes (line 38240) | function getLayoutAttributes() {
function getTransformAttributes (line 38287) | function getTransformAttributes(type) {
function getFramesAttributes (line 38307) | function getFramesAttributes() {
function formatAttributes (line 38317) | function formatAttributes(attrs) {
function mergeValTypeAndRole (line 38324) | function mergeValTypeAndRole(attrs) {
function formatArrayContainers (line 38356) | function formatArrayContainers(attrs) {
function assignPolarLayoutAttrs (line 38375) | function assignPolarLayoutAttrs(layoutAttributes) {
function handleBasePlotModule (line 38386) | function handleBasePlotModule(layoutAttributes, _module, astr) {
function insertAttrs (line 38394) | function insertAttrs(baseAttrs, newAttrs, astr) {
function registerTraceModule (line 38449) | function registerTraceModule(newModule) {
function registerTransformModule (line 38457) | function registerTransformModule(newModule) {
function registerComponentModule (line 38491) | function registerComponentModule(newModule) {
function overlappingDomain (line 38554) | function overlappingDomain(xDomain, yDomain, domains) {
function xLinePath (line 38780) | function xLinePath(y, showThis) {
function yLinePath (line 38785) | function yLinePath(x, showThis) {
function shouldShowLine (line 38878) | function shouldShowLine(ax, counterAx, side) {
function findCounterAxes (line 38884) | function findCounterAxes(gd, ax, axList) {
function findLineWidth (line 38903) | function findLineWidth(gd, axes, side) {
function findCounterAxisLineWidth (line 38913) | function findCounterAxisLineWidth(gd, ax, subplotCounterLineWidth,
function toImage (line 39084) | function toImage(gd, opts) {
function crawl (line 39315) | function crawl(objIn, objOut, schema, list, base, path) {
function fillLayoutSchema (line 39399) | function fillLayoutSchema(schema, dataOut) {
function inBase (line 39462) | function inBase(base) {
function format (line 39468) | function format(code, base, path, valIn, valOut) {
function isInSchema (line 39501) | function isInSchema(schema, key) {
function getNestedSchema (line 39513) | function getNestedSchema(schema, key) {
function splitKey (line 39519) | function splitKey(key) {
function convertPathToAttributeString (line 39531) | function convertPathToAttributeString(path) {
function getPad (line 40360) | function getPad(item) {
function addItem (line 40376) | function addItem(i) {
function autoShiftNumericBins (line 40548) | function autoShiftNumericBins(binStart, data, ax, dataMin, dataMax) {
function autoShiftMonthBins (line 40597) | function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) {
function arrayTicks (line 40730) | function arrayTicks(ax) {
function roundDTick (line 40780) | function roundDTick(roughDTick, base, roundingSet) {
function autoTickRound (line 40892) | function autoTickRound(ax) {
function isHidden (line 41077) | function isHidden(showAttr) {
function tickTextObj (line 41105) | function tickTextObj(ax, x, text) {
function formatDate (line 41119) | function formatDate(ax, out, hover, extraPrecision) {
function formatLog (line 41175) | function formatLog(ax, out, hover, extraPrecision, hideexp) {
function formatCategory (line 41222) | function formatCategory(ax, out) {
function formatLinear (line 41228) | function formatLinear(ax, out, hover, extraPrecision, hideexp) {
function numFormat (line 41244) | function numFormat(v, ax, fmtoverride, hover) {
function hasAx2 (line 41385) | function hasAx2(sp, ax2) {
function clipEnds (line 41646) | function clipEnds(d) {
function drawTicks (line 41652) | function drawTicks(container, tickpath) {
function drawLabels (line 41667) | function drawLabels(container, position) {
function drawAxTitle (line 41898) | function drawAxTitle() {
function traceHasBarsOrFill (line 41978) | function traceHasBarsOrFill(trace, subplot) {
function drawGrid (line 41984) | function drawGrid(plotinfo, counteraxis, subplot) {
function makeAxisGroups (line 42102) | function makeAxisGroups(gd, traces) {
function mergeAxisGroups (line 42141) | function mergeAxisGroups(intoSet, fromSet) {
function swapAxisGroup (line 42147) | function swapAxisGroup(gd, xIds, yIds) {
function swapAxisAttrs (line 42211) | function swapAxisAttrs(layout, key, xFullAxes, yFullAxes) {
function linearOK (line 42263) | function linearOK(array) {
function moreDates (line 42278) | function moreDates(a, calendar) {
function category (line 42296) | function category(a) {
function coerce2 (line 42358) | function coerce2(attr, dflt) {
function listNames (line 42482) | function listNames(gd, axLetter, only2d) {
function getConstraintOpts (line 42732) | function getConstraintOpts(constraintGroups, thisID, allAxisIds, layoutO...
function updateConstraintGroups (line 42781) | function updateConstraintGroups(constraintGroups, thisGroup, thisID, sca...
function updateDomain (line 43015) | function updateDomain(ax, factor) {
function recomputeAxisLists (line 43098) | function recomputeAxisLists() {
function zoomPrep (line 43217) | function zoomPrep(e, startX, startY) {
function zoomMove (line 43236) | function zoomMove(dx0, dy0) {
function zoomDone (line 43307) | function zoomDone(dragged, numClicks) {
function dragDone (line 43327) | function dragDone(dragged, numClicks) {
function zoomWheel (line 43376) | function zoomWheel(e) {
function plotDrag (line 43471) | function plotDrag(dx, dy) {
function ticksAndAnnotations (line 43560) | function ticksAndAnnotations(ns, ew) {
function doubleClick (line 43609) | function doubleClick() {
function dragTail (line 43679) | function dragTail(zoommode) {
function updateSubplots (line 43698) | function updateSubplots(viewBox) {
function makeDragger (line 43798) | function makeDragger(plotinfo, dragClass, cursor, x, y, w, h) {
function isDirectionActive (line 43813) | function isDirectionActive(axList, activeVal) {
function getEndText (line 43820) | function getEndText(ax, end) {
function zoomAxRanges (line 43841) | function zoomAxRanges(axList, r0Fraction, r1Fraction, updates, linkedAxe...
function dragAxList (line 43869) | function dragAxList(axList, pix) {
function dZoom (line 43886) | function dZoom(d) {
function getDragCursor (line 43891) | function getDragCursor(nsew, dragmode) {
function makeZoombox (line 43900) | function makeZoombox(zoomlayer, lum, xs, ys, path0) {
function makeCorners (line 43911) | function makeCorners(zoomlayer, xs, ys) {
function clearSelect (line 43924) | function clearSelect(zoomlayer) {
function updateZoombox (line 43931) | function updateZoombox(zb, corners, box, path0, dimmed, lum) {
function removeZoombox (line 43946) | function removeZoombox(gd) {
function isSelectOrLasso (line 43952) | function isSelectOrLasso(dragmode) {
function xCorners (line 43958) | function xCorners(box, y0) {
function yCorners (line 43966) | function yCorners(box, x0) {
function xyCorners (line 43974) | function xyCorners(box) {
function calcLinks (line 43987) | function calcLinks(constraintGroups, xIDs, yIDs) {
function plotOne (line 44298) | function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnComplete...
function makeSubplotData (line 44435) | function makeSubplotData(gd) {
function makeSubplotLayer (line 44468) | function makeSubplotLayer(plotinfo) {
function purgeSubplotLayers (line 44558) | function purgeSubplotLayers(layers, fullLayout) {
function joinLayer (line 44596) | function joinLayer(parent, nodeType, className) {
function axSort (line 45137) | function axSort(a, b) {
function coerce (line 45158) | function coerce(attr, dflt) {
function getCounterAxes (line 45162) | function getCounterAxes(axLetter) {
function getOverlayableAxes (line 45169) | function getOverlayableAxes(axLetter, axName) {
function flattenUniqueSort (line 45307) | function flattenUniqueSort(axisLetter, sortFunction, data) {
function getAxId (line 45489) | function getAxId(ax) { return ax._id; }
function axValue (line 45570) | function axValue(ax) {
function ascending (line 45575) | function ascending(a, b) { return a - b; }
function fillSelectionItem (line 45683) | function fillSelectionItem(selection, searchInfo) {
function fromLog (line 45728) | function fromLog(v) {
function toLog (line 45768) | function toLog(v, clip) {
function dt2ms (line 45788) | function dt2ms(v, _, calendar) {
function ms2dt (line 45802) | function ms2dt(v, r, calendar) {
function getCategoryName (line 45806) | function getCategoryName(v) {
function setCategoryIndex (line 45823) | function setCategoryIndex(v) {
function getCategoryIndex (line 45843) | function getCategoryIndex(v) {
function l2p (line 45854) | function l2p(v) {
function p2l (line 45861) | function p2l(px) { return (px - ax._b) / ax._m; }
function getShowAttrDflt (line 46233) | function getShowAttrDflt(containerIn) {
function computeUpdates (line 46390) | function computeUpdates(layout) {
function computeAffectedSubplots (line 46424) | function computeAffectedSubplots(fullLayout, updatedAxisIds, updates) {
function updateLayoutObjs (line 46469) | function updateLayoutObjs() {
function ticksAndAnnotations (line 46489) | function ticksAndAnnotations(xa, ya) {
function unsetSubplotTransform (line 46518) | function unsetSubplotTransform(subplot) {
function updateSubplot (line 46544) | function updateSubplot(subplot, progress) {
function transitionComplete (line 46630) | function transitionComplete() {
function transitionInterrupt (line 46651) | function transitionInterrupt() {
function doFrame (line 46677) | function doFrame() {
function setAutoType (line 46745) | function setAutoType(ax, data) {
function getFirstNonEmptyTrace (line 46796) | function getFirstNonEmptyTrace(data, id, axLetter) {
function getBoxPosLetter (line 46811) | function getBoxPosLetter(trace) {
function isBoxWithoutPositionCoords (line 46815) | function isBoxWithoutPositionCoords(trace, axLetter) {
function bindingValueHasChanged (line 47049) | function bindingValueHasChanged(gd, binding, cache) {
function computeAnimateBindings (line 47140) | function computeAnimateBindings(gd, args) {
function computeLayoutBindings (line 47150) | function computeLayoutBindings(gd, args) {
function computeDataBindings (line 47170) | function computeDataBindings(gd, args) {
function crawl (line 47239) | function crawl(attrs, callback, path, depth) {
function xformMatrix (line 47345) | function xformMatrix(m, v) {
function project (line 47358) | function project(camera, v) {
function positionPlayWithData (line 47872) | function positionPlayWithData(gd, container) {
function remapTransformedArrays (line 48070) | function remapTransformedArrays(cd0, newTrace) {
function locateColorAttrs (line 48292) | function locateColorAttrs(attr, attrName, attrs, level) {
function pushModule (line 48332) | function pushModule(fullTrace) {
function coerce (line 48419) | function coerce(attr, dflt) {
function coerce (line 48451) | function coerce(attr, dflt) {
function coerce (line 48464) | function coerce(attr, dflt) {
function coerce (line 48477) | function coerce(attr, dflt) {
function coerce (line 48495) | function coerce(attr, dflt) {
function coerceSubplotAttr (line 48499) | function coerceSubplotAttr(subplotType, subplotAttr) {
function applyTransforms (line 48603) | function applyTransforms(fullTrace, fullData, layout, fullLayout) {
function coerce (line 48627) | function coerce(attr, dflt) {
function calculateReservedMargins (line 48756) | function calculateReservedMargins(margins) {
function stripObj (line 49082) | function stripObj(d) {
function prepareTransitions (line 49439) | function prepareTransitions() {
function executeCallbacks (line 49494) | function executeCallbacks(list) {
function flushCallbacks (line 49503) | function flushCallbacks(list) {
function executeTransitions (line 49512) | function executeTransitions() {
function completeTransition (line 49599) | function completeTransition(callback) {
function interruptPreviousTransitions (line 49621) | function interruptPreviousTransitions() {
function initCategories (line 49779) | function initCategories(axList) {
function filterVisible (line 49802) | function filterVisible(calcDataIn) {
function mergeAttrs (line 49905) | function mergeAttrs(axisName, nonCommonAttrs) {
function render (line 50039) | function render(_container) {
function exports (line 50796) | function exports() {
function exports (line 51058) | function exports() {
function exports (line 51463) | function exports(_inputConfig, _container) {
function execute (line 51541) | function execute(command, action) {
function findArrayRegexps (line 51696) | function findArrayRegexps(_module) {
function getTraceType (line 51774) | function getTraceType(traceType) {
function cloneLayoutOverride (line 51798) | function cloneLayoutOverride(tileClass) {
function keyIsAxis (line 51834) | function keyIsAxis(keyName) {
function downloadImage (line 51975) | function downloadImage(gd, opts) {
function svgToImg (line 52158) | function svgToImg(opts) {
function toImage (line 52302) | function toImage(gd, opts) {
function htmlEntityDecode (line 52377) | function htmlEntityDecode(s) {
function xmlEntityEncode (line 52389) | function xmlEntityEncode(str) {
function coerce (line 52777) | function coerce(attr, dflt) {
function coerce (line 53027) | function coerce(attr, dflt) {
function roundWithLine (line 53156) | function roundWithLine(v) {
function expandToVisible (line 53163) | function expandToVisible(v, vc) {
function appendBarText (line 53206) | function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) {
function getTransformToMoveInsideBar (line 53318) | function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) {
function getTransformToMoveOutsideBar (line 53403) | function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientatio...
function getTransform (line 53463) | function getTransform(textX, textY, targetX, targetY, scale, rotate) {
function getText (line 53485) | function getText(trace, index) {
function getTextPosition (line 53490) | function getTextPosition(trace, index) {
function getTextFont (line 53495) | function getTextFont(trace, index, defaultValue) {
function getInsideTextFont (line 53500) | function getInsideTextFont(trace, index, defaultValue) {
function getOutsideTextFont (line 53505) | function getOutsideTextFont(trace, index, defaultValue) {
function getFontValue (line 53510) | function getFontValue(attributeDefinition, attributeValue, index, defaul...
function getValue (line 53527) | function getValue(arrayOrScalar, index) {
function coerceString (line 53534) | function coerceString(attributeDefinition, value, defaultValue) {
function coerceEnumerated (line 53547) | function coerceEnumerated(attributeDefinition, value, defaultValue) {
function coerceNumber (line 53557) | function coerceNumber(attributeDefinition, value, defaultValue) {
function coerceColor (line 53574) | function coerceColor(attributeDefinition, value, defaultValue) {
function setGroupPositions (line 53639) | function setGroupPositions(gd, pa, sa, calcTraces) {
function setGroupPositionsInOverlayMode (line 53693) | function setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces) {
function setGroupPositionsInGroupMode (line 53725) | function setGroupPositionsInGroupMode(gd, pa, sa, calcTraces) {
function setGroupPositionsInStackOrRelativeMode (line 53748) | function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces) {
function setOffsetAndWidth (line 53786) | function setOffsetAndWidth(gd, pa, sieve) {
function setOffsetAndWidthInGroupMode (line 53828) | function setOffsetAndWidthInGroupMode(gd, pa, sieve) {
function applyAttributes (line 53878) | function applyAttributes(sieve) {
function setBarCenterAndWidth (line 53962) | function setBarCenterAndWidth(gd, pa, sieve) {
function updatePositionAxis (line 53989) | function updatePositionAxis(gd, pa, sieve, allowMinDtick) {
function expandRange (line 54038) | function expandRange(range, newValue) {
function setBaseAndTop (line 54046) | function setBaseAndTop(gd, sa, sieve) {
function stackBars (line 54074) | function stackBars(gd, sa, sieve) {
function sieveBars (line 54113) | function sieveBars(gd, sa, sieve) {
function normalizeBars (line 54128) | function normalizeBars(gd, sa, sieve) {
function getAxisLetter (line 54178) | function getAxisLetter(ax) {
function Sieve (line 54210) | function Sieve(traces, separateNegativeValues, dontMergeOverlappingData) {
function getCdModule (line 54621) | function getCdModule(calcdata, _module) {
function nextDefaultColor (line 54768) | function nextDefaultColor(index) {
function coerce (line 54803) | function coerce(attr, dflt) {
function coerce (line 54968) | function coerce(attr, dflt) {
function handleMouseOver (line 55066) | function handleMouseOver(evt) {
function handleMouseOut (line 55128) | function handleMouseOut(evt) {
function handleClick (line 55141) | function handleClick() {
function arc (line 55169) | function arc(start, finish, cw, scale) {
function transformInsideText (line 55344) | function transformInsideText(textBB, pt, cd0) {
function getInscribedRadiusFraction (line 55400) | function getInscribedRadiusFraction(pt, cd0) {
function transformOutsideText (line 55407) | function transformOutsideText(textBB, pt) {
function scootLabels (line 55426) | function scootLabels(quadrants, trace) {
function scalePies (line 55540) | function scalePies(cdpie, plotSize) {
function setCoords (line 55605) | function setCoords(cd) {
function maxExtent (line 55678) | function maxExtent(tilt, tiltAxisFraction, depth) {
function coerce (line 56375) | function coerce(attr, dflt) {
function getPt (line 56826) | function getPt(index) {
function getTolerance (line 56834) | function getTolerance(pt) {
function ptDist (line 56840) | function ptDist(pt1, pt2) {
function createFills (line 57223) | function createFills(gd, scatterlayer, plotinfo) {
function plotOne (line 57266) | function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, tr...
function selectMarkers (line 57656) | function selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll) {
Condensed preview — 253 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,698K chars).
[
{
"path": ".github/workflows/ci.yml",
"chars": 1033,
"preview": "name: CI\n\non:\n push:\n pull_request:\n\njobs:\n test:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: false\n "
},
{
"path": ".gitignore",
"chars": 37,
"preview": ".bundle/\nlog/*.log\npkg/\nGemfile.lock\n"
},
{
"path": ".rspec",
"chars": 30,
"preview": "--color\n--require spec_helper\n"
},
{
"path": "CHANGELOG.md",
"chars": 9643,
"preview": "## v2.10.0 (2025-07-22)\n### Changes\n- Update Rails to 7.0\n- Use `Bundler.with_unbundled_env` instead of deprecated `Bund"
},
{
"path": "Gemfile",
"chars": 913,
"preview": "source 'https://rubygems.org'\n\n# Declare your gem's dependencies in barbeque.gemspec.\n# Bundler will treat runtime depen"
},
{
"path": "MIT-LICENSE",
"chars": 1055,
"preview": "Copyright 2016 Takashi Kokubun\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this sof"
},
{
"path": "README.md",
"chars": 3252,
"preview": "# Barbeque [](https"
},
{
"path": "Rakefile",
"chars": 1975,
"preview": "begin\n require 'bundler/setup'\nrescue LoadError\n puts 'You must `gem install bundler` and `bundle install` to run rake"
},
{
"path": "app/assets/config/barbeque_manifest.js",
"chars": 95,
"preview": "//= link_directory ../javascripts/barbeque .js\n//= link_directory ../stylesheets/barbeque .css\n"
},
{
"path": "app/assets/images/barbeque/.keep",
"chars": 0,
"preview": ""
},
{
"path": "app/assets/javascripts/barbeque/application.js",
"chars": 780,
"preview": "// This is a manifest file that'll be compiled into application.js, which will include all the files\n// listed below.\n//"
},
{
"path": "app/assets/javascripts/barbeque/job_definitions.js",
"chars": 657,
"preview": "jQuery(function($) {\n if (!document.querySelector('.barbeque_job_definitions_controller')) {\n return;\n }\n\n $('.use"
},
{
"path": "app/assets/javascripts/barbeque/job_queues.js",
"chars": 4120,
"preview": "jQuery(function($) {\n if (!document.querySelector('.barbeque_job_queues_controller')) {\n return;\n }\n const sqsDiv "
},
{
"path": "app/assets/stylesheets/barbeque/application.css",
"chars": 469,
"preview": "/*\n *= require bootstrap\n *= require AdminLTE\n *= require skins/skin-blue\n\n *= require barbeque/job_definitions\n */\n\n:ro"
},
{
"path": "app/assets/stylesheets/barbeque/job_definitions.css",
"chars": 1114,
"preview": ".barbeque_job_definitions_controller .use_slack_notification_wrapper label {\n font-weight: normal;\n}\n\n.barbeque_job_def"
},
{
"path": "app/controllers/barbeque/api/application_controller.rb",
"chars": 906,
"preview": "require 'garage'\n\nclass Barbeque::Api::ApplicationController < ActionController::API\n before_action :force_json_format\n"
},
{
"path": "app/controllers/barbeque/api/job_executions_controller.rb",
"chars": 2085,
"preview": "require 'barbeque/maintenance'\n\nclass Barbeque::Api::JobExecutionsController < Barbeque::Api::ApplicationController\n in"
},
{
"path": "app/controllers/barbeque/api/job_retries_controller.rb",
"chars": 738,
"preview": "class Barbeque::Api::JobRetriesController < Barbeque::Api::ApplicationController\n include Garage::RestfulActions\n\n # h"
},
{
"path": "app/controllers/barbeque/api/revision_locks_controller.rb",
"chars": 920,
"preview": "class Barbeque::Api::RevisionLocksController < Barbeque::Api::ApplicationController\n include Garage::RestfulActions\n\n "
},
{
"path": "app/controllers/barbeque/application_controller.rb",
"chars": 123,
"preview": "module Barbeque\n class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\n end\ne"
},
{
"path": "app/controllers/barbeque/apps_controller.rb",
"chars": 954,
"preview": "class Barbeque::AppsController < Barbeque::ApplicationController\n def index\n @apps = Barbeque::App.all\n end\n\n def "
},
{
"path": "app/controllers/barbeque/job_definitions_controller.rb",
"chars": 3141,
"preview": "class Barbeque::JobDefinitionsController < Barbeque::ApplicationController\n def index\n @job_definitions = Barbeque::"
},
{
"path": "app/controllers/barbeque/job_executions_controller.rb",
"chars": 1123,
"preview": "class Barbeque::JobExecutionsController < Barbeque::ApplicationController\n ID_REGEXP = /\\A[0-9]+\\z/\n\n def show\n if "
},
{
"path": "app/controllers/barbeque/job_queues_controller.rb",
"chars": 4741,
"preview": "require 'aws-sdk-cloudwatch'\nrequire 'aws-sdk-sqs'\nrequire 'barbeque/config'\n\nclass Barbeque::JobQueuesController < Barb"
},
{
"path": "app/controllers/barbeque/job_retries_controller.rb",
"chars": 606,
"preview": "class Barbeque::JobRetriesController < Barbeque::ApplicationController\n def show\n @job_retry = Barbeque::JobRetry.fi"
},
{
"path": "app/controllers/barbeque/monitors_controller.rb",
"chars": 1663,
"preview": "class Barbeque::MonitorsController < Barbeque::ApplicationController\n def index\n now = Time.zone.now\n from = 6.ho"
},
{
"path": "app/controllers/barbeque/sns_subscriptions_controller.rb",
"chars": 1320,
"preview": "class Barbeque::SnsSubscriptionsController < Barbeque::ApplicationController\n def index\n @sns_subscriptions = Barbeq"
},
{
"path": "app/helpers/barbeque/application_helper.rb",
"chars": 53,
"preview": "module Barbeque\n module ApplicationHelper\n end\nend\n"
},
{
"path": "app/helpers/barbeque/job_definitions_helper.rb",
"chars": 336,
"preview": "module Barbeque::JobDefinitionsHelper\n def distance_of_time(from, to)\n return '' if from.nil? || to.nil?\n\n secs "
},
{
"path": "app/helpers/barbeque/job_executions_helper.rb",
"chars": 435,
"preview": "module Barbeque::JobExecutionsHelper\n def status_label(status)\n color =\n case status\n when 'success'\n "
},
{
"path": "app/models/barbeque/api/application_resource.rb",
"chars": 202,
"preview": "require 'garage'\n\nclass Barbeque::Api::ApplicationResource\n include Garage::Representer\n include Garage::Authorizable\n"
},
{
"path": "app/models/barbeque/api/database_maintenance_resource.rb",
"chars": 204,
"preview": "class Barbeque::Api::DatabaseMaintenanceResource\n include Garage::Representer\n\n property :message\n\n delegate :message"
},
{
"path": "app/models/barbeque/api/job_execution_resource.rb",
"chars": 519,
"preview": "class Barbeque::Api::JobExecutionResource < Barbeque::Api::ApplicationResource\n property :message_id\n property :status"
},
{
"path": "app/models/barbeque/api/job_retry_resource.rb",
"chars": 167,
"preview": "class Barbeque::Api::JobRetryResource < Barbeque::Api::ApplicationResource\n property :message_id\n\n property :status\n\n "
},
{
"path": "app/models/barbeque/api/revision_lock_resource.rb",
"chars": 181,
"preview": "class Barbeque::Api::RevisionLockResource < Barbeque::Api::ApplicationResource\n property :revision\n\n def revision\n "
},
{
"path": "app/models/barbeque/app.rb",
"chars": 221,
"preview": "class Barbeque::App < Barbeque::ApplicationRecord\n validates :name, presence: true, uniqueness: true\n validates :docke"
},
{
"path": "app/models/barbeque/application_record.rb",
"chars": 104,
"preview": "module Barbeque\n class ApplicationRecord < ActiveRecord::Base\n self.abstract_class = true\n end\nend\n"
},
{
"path": "app/models/barbeque/docker_container.rb",
"chars": 66,
"preview": "class Barbeque::DockerContainer < Barbeque::ApplicationRecord\nend\n"
},
{
"path": "app/models/barbeque/ecs_hako_task.rb",
"chars": 62,
"preview": "class Barbeque::EcsHakoTask < Barbeque::ApplicationRecord\nend\n"
},
{
"path": "app/models/barbeque/job_definition.rb",
"chars": 1239,
"preview": "class Barbeque::JobDefinition < Barbeque::ApplicationRecord\n belongs_to :app\n has_many :job_executions\n has_many :sns"
},
{
"path": "app/models/barbeque/job_execution.rb",
"chars": 1071,
"preview": "class Barbeque::JobExecution < Barbeque::ApplicationRecord\n belongs_to :job_definition\n belongs_to :job_queue\n has_on"
},
{
"path": "app/models/barbeque/job_queue.rb",
"chars": 1462,
"preview": "require 'barbeque/maintenance'\n\nclass Barbeque::JobQueue < Barbeque::ApplicationRecord\n SQS_NAME_PREFIX = ENV['BARBEQUE"
},
{
"path": "app/models/barbeque/job_retry.rb",
"chars": 587,
"preview": "class Barbeque::JobRetry < Barbeque::ApplicationRecord\n belongs_to :job_execution\n has_one :job_definition, through: :"
},
{
"path": "app/models/barbeque/retry_config.rb",
"chars": 760,
"preview": "class Barbeque::RetryConfig < Barbeque::ApplicationRecord\n belongs_to :job_definition\n\n validates :retry_limit, numeri"
},
{
"path": "app/models/barbeque/slack_notification.rb",
"chars": 151,
"preview": "class Barbeque::SlackNotification < Barbeque::ApplicationRecord\n belongs_to :job_definition, optional: true\n\n validate"
},
{
"path": "app/models/barbeque/sns_subscription.rb",
"chars": 577,
"preview": "module Barbeque\n class SnsSubscription < ApplicationRecord\n belongs_to :job_queue\n belongs_to :job_definition\n "
},
{
"path": "app/models/concerns/.keep",
"chars": 0,
"preview": ""
},
{
"path": "app/services/barbeque/message_enqueuing_service.rb",
"chars": 1585,
"preview": "require 'aws-sdk-sqs'\n\nclass Barbeque::MessageEnqueuingService\n DEFAULT_QUEUE = ENV['BARBEQUE_DEFAULT_QUEUE'] || 'defau"
},
{
"path": "app/services/barbeque/message_retrying_service.rb",
"chars": 731,
"preview": "require 'aws-sdk-sqs'\n\nclass Barbeque::MessageRetryingService\n DEFAULT_DELAY_SECONDS = 0\n\n def self.sqs_client\n @sq"
},
{
"path": "app/services/barbeque/sns_subscription_service.rb",
"chars": 3774,
"preview": "require 'aws-sdk-sns'\nrequire 'aws-sdk-sqs'\n\nclass Barbeque::SnsSubscriptionService\n def self.sqs_client\n @sqs_clien"
},
{
"path": "app/views/barbeque/apps/_form.html.haml",
"chars": 975,
"preview": ".box.box-primary\n .box-header\n %h3.box-title.with_padding\n #{action_name.capitalize} Application\n\n .box-body\n "
},
{
"path": "app/views/barbeque/apps/edit.html.haml",
"chars": 234,
"preview": "- content_for(:title, \"Edit #{@app.name} - Barbeque\")\n- content_for(:header) do\n %ol.breadcrumb\n %li= link_to('Home'"
},
{
"path": "app/views/barbeque/apps/index.html.haml",
"chars": 672,
"preview": "- content_for(:title, 'Barbeque')\n- content_for(:header) do\n %ol.breadcrumb\n %li.active Home\n\n.box.box-primary\n .bo"
},
{
"path": "app/views/barbeque/apps/new.html.haml",
"chars": 177,
"preview": "- content_for(:title, 'New application - Barbeque')\n- content_for(:header) do\n %ol.breadcrumb\n %li= link_to('Home', "
},
{
"path": "app/views/barbeque/apps/show.html.haml",
"chars": 1420,
"preview": "- content_for(:title, \"#{@app.name} - Barbeque\")\n- content_for(:header) do\n %ol.breadcrumb\n %li= link_to('Home', roo"
},
{
"path": "app/views/barbeque/job_definitions/_form.html.haml",
"chars": 1604,
"preview": ".box.box-primary\n .box-header\n %h3.box-title.with_padding\n #{action_name.capitalize} Job Definition\n\n .box-bod"
},
{
"path": "app/views/barbeque/job_definitions/_retry_configuration_field.html.haml",
"chars": 1220,
"preview": "= fields_for(job_definition) do |job_definition_f|\n = job_definition_f.fields_for(:retry_config) do |f|\n = f.hidden_"
},
{
"path": "app/views/barbeque/job_definitions/_slack_notification_field.html.haml",
"chars": 1419,
"preview": "= fields_for job_definition do |job_definition_f|\n = job_definition_f.fields_for :slack_notification do |f|\n = f.hid"
},
{
"path": "app/views/barbeque/job_definitions/edit.html.haml",
"chars": 387,
"preview": "- content_for(:title, \"Edit job definition #{@job_definition.job} - Barbeque\")\n- content_for(:header) do\n %ol.breadcrum"
},
{
"path": "app/views/barbeque/job_definitions/index.html.haml",
"chars": 823,
"preview": "- content_for(:title, 'Job definitions - Barbeque')\n- content_for(:header) do\n %ol.breadcrumb\n %li= link_to('Home', "
},
{
"path": "app/views/barbeque/job_definitions/new.html.haml",
"chars": 299,
"preview": "- content_for(:title, 'New job definition - Barbeque')\n- content_for(:header) do\n %ol.breadcrumb\n %li= link_to('Home"
},
{
"path": "app/views/barbeque/job_definitions/show.html.haml",
"chars": 4478,
"preview": "- content_for(:title, \"#{@job_definition.job} - Barbeque\")\n- content_for(:header) do\n %ol.breadcrumb\n %li= link_to('"
},
{
"path": "app/views/barbeque/job_definitions/stats.html.haml",
"chars": 1895,
"preview": "- content_for(:title, \"#{@job_definition.job} statistics - Barbeque\")\n- content_for(:header) do\n %ol.breadcrumb\n %li"
},
{
"path": "app/views/barbeque/job_executions/show.html.haml",
"chars": 3384,
"preview": "- content_for(:title, \"Job execution ##{@job_execution.id} of #{@job_definition.job} - Barbeque\")\n- content_for(:header)"
},
{
"path": "app/views/barbeque/job_queues/_form.html.haml",
"chars": 876,
"preview": ".box.box-primary\n .box-header\n %h3.box-title.with_padding\n #{action_name.capitalize} Job Queue\n\n .box-body\n "
},
{
"path": "app/views/barbeque/job_queues/edit.html.haml",
"chars": 328,
"preview": "- content_for(:title, \"Edit #{@job_queue.name} job queue - Barbeque\")\n- content_for(:header) do\n %ol.breadcrumb\n %li"
},
{
"path": "app/views/barbeque/job_queues/index.html.haml",
"chars": 698,
"preview": "- content_for(:title, 'Job queues - Barbeque')\n- content_for(:header) do\n %ol.breadcrumb\n %li= link_to('Home', root_"
},
{
"path": "app/views/barbeque/job_queues/new.html.haml",
"chars": 229,
"preview": "- content_for(:title, 'New job queue - Barbeque')\n- content_for(:header) do\n %ol.breadcrumb\n %li= link_to('Home', ro"
},
{
"path": "app/views/barbeque/job_queues/show.html.haml",
"chars": 968,
"preview": "- content_for(:title, \"#{@job_queue.name} job queue - Barbeque\")\n- content_for(:header) do\n %ol.breadcrumb\n %li= lin"
},
{
"path": "app/views/barbeque/job_retries/show.html.haml",
"chars": 2220,
"preview": "- content_for(:title, \"Job retry ##{@job_retry.id} of #{@job_definition.job} - Barbeque\")\n- content_for(:header) do\n %o"
},
{
"path": "app/views/barbeque/monitors/index.html.haml",
"chars": 1951,
"preview": "- content_for(:title, 'Monitors - Barbeque')\n- content_for(:header) do\n %ol.breadcrumb\n %li= link_to('Home', root_pa"
},
{
"path": "app/views/barbeque/sns_subscriptions/_form.html.haml",
"chars": 1493,
"preview": ".box.box-primary\n .box-header\n %h3.box-title.with_padding\n #{action_name.capitalize} SNS Subscription\n\n .box-b"
},
{
"path": "app/views/barbeque/sns_subscriptions/edit.html.haml",
"chars": 446,
"preview": "- content_for(:title, \"Edit SNS subscription between #{@sns_subscription.topic_arn} and #{@sns_subscription.job_queue.na"
},
{
"path": "app/views/barbeque/sns_subscriptions/index.html.haml",
"chars": 862,
"preview": "- content_for(:title, 'SNS subscriptions - Barbeque')\n- content_for(:header) do\n %ol.breadcrumb\n %li= link_to('Home'"
},
{
"path": "app/views/barbeque/sns_subscriptions/new.html.haml",
"chars": 257,
"preview": "- content_for(:title, 'New SNS subscription - Barbeque')\n- content_for(:header) do\n %ol.breadcrumb\n %li= link_to('Ho"
},
{
"path": "app/views/barbeque/sns_subscriptions/show.html.haml",
"chars": 1245,
"preview": "- content_for(:title, \"SNS subscription between #{@sns_subscription.topic_arn} and #{@sns_subscription.job_queue.name} -"
},
{
"path": "app/views/layouts/barbeque/_header.html.haml",
"chars": 286,
"preview": "%header.main-header\n %a.logo{ href: '/' }\n %span.logo-mini\n %b> BBQ\n %span.logo-lg\n %b> Barbeque\n\n %na"
},
{
"path": "app/views/layouts/barbeque/_sidebar.html.haml",
"chars": 933,
"preview": "%aside.main-sidebar\n %section.sidebar\n %ul.sidebar-menu\n %li.header Menu\n %li{ class: ('active' if control"
},
{
"path": "app/views/layouts/barbeque/application.html.haml",
"chars": 1376,
"preview": "!!!\n%html\n %head\n %meta{ content: 'text/html; charset=UTF-8', 'http-equiv': 'Content-Type' }\n %meta{ charset: 'UT"
},
{
"path": "app/views/layouts/barbeque/apps.html.haml",
"chars": 117,
"preview": "- content_for(:header) do\n %h1\n .fa.fa-users\n Applications\n\n= render template: 'layouts/barbeque/application'\n"
},
{
"path": "app/views/layouts/barbeque/job_definitions.html.haml",
"chars": 120,
"preview": "- content_for(:header) do\n %h1\n .fa.fa-tasks\n Job Definitions\n\n= render template: 'layouts/barbeque/application'\n"
},
{
"path": "app/views/layouts/barbeque/job_executions.html.haml",
"chars": 102,
"preview": "- content_for(:header) do\n %h1\n Job Executions\n\n= render template: 'layouts/barbeque/application'\n"
},
{
"path": "app/views/layouts/barbeque/job_queues.html.haml",
"chars": 111,
"preview": "- content_for(:header) do\n %h1\n .fa.fa-cubes\n Queues\n\n= render template: 'layouts/barbeque/application'\n"
},
{
"path": "app/views/layouts/barbeque/job_retries.html.haml",
"chars": 99,
"preview": "- content_for(:header) do\n %h1\n Job Retries\n\n= render template: 'layouts/barbeque/application'\n"
},
{
"path": "app/views/layouts/barbeque/monitors.html.haml",
"chars": 118,
"preview": "- content_for(:header) do\n %h1\n .fa.fa-tachometer\n Monitors\n\n= render template: 'layouts/barbeque/application'\n"
},
{
"path": "app/views/layouts/barbeque/sns_subscriptions.html.haml",
"chars": 134,
"preview": "- content_for(:header) do\n %h1\n %i.fa.fa-calendar-plus-o\n SNS Subscriptions\n\n= render template: 'layouts/barbeque"
},
{
"path": "barbeque.gemspec",
"chars": 1334,
"preview": "$:.push File.expand_path(\"../lib\", __FILE__)\nrequire \"barbeque/version\"\n\nGem::Specification.new do |s|\n s.name ="
},
{
"path": "bin/rails",
"chars": 489,
"preview": "#!/usr/bin/env ruby\n# This command will automatically be run when you run \"rails\" with Rails gems\n# installed from the r"
},
{
"path": "compose.yaml",
"chars": 213,
"preview": "services:\n db:\n image: mysql:8.0\n ports:\n - 3306:3306\n environment:\n MYSQL_ROOT_PASSWORD: barbeque_r"
},
{
"path": "config/initializers/garage.rb",
"chars": 105,
"preview": "require 'garage'\n\nGarage.configure {}\nGarage.configuration.strategy = Garage::Strategy::NoAuthentication\n"
},
{
"path": "config/routes.rb",
"chars": 1100,
"preview": "Barbeque::Engine.routes.draw do\n root to: 'apps#index'\n\n resources :apps, except: :index\n\n resources :job_definitions"
},
{
"path": "db/migrate/20160217020910_create_job_queues.rb",
"chars": 357,
"preview": "class CreateJobQueues < ActiveRecord::Migration[5.0]\n def change\n create_table :job_queues, options: 'ENGINE=InnoDB "
},
{
"path": "db/migrate/20160219010912_create_job_executions.rb",
"chars": 365,
"preview": "class CreateJobExecutions < ActiveRecord::Migration[5.0]\n def change\n create_table :job_executions, options: 'ENGINE"
},
{
"path": "db/migrate/20160223060807_create_apps.rb",
"chars": 343,
"preview": "class CreateApps < ActiveRecord::Migration[5.0]\n def change\n create_table :apps, options: 'ENGINE=InnoDB ROW_FORMAT="
},
{
"path": "db/migrate/20160223183348_create_job_definitions.rb",
"chars": 414,
"preview": "class CreateJobDefinitions < ActiveRecord::Migration[5.0]\n def change\n create_table :job_definitions, options: 'ENGI"
},
{
"path": "db/migrate/20160225020801_add_job_definition_id_to_job_executions.rb",
"chars": 207,
"preview": "class AddJobDefinitionIdToJobExecutions < ActiveRecord::Migration[5.0]\n def change\n add_column :job_executions, :job"
},
{
"path": "db/migrate/20160412083604_add_finished_at_to_job_executions.rb",
"chars": 145,
"preview": "class AddFinishedAtToJobExecutions < ActiveRecord::Migration[5.0]\n def change\n add_column :job_executions, :finished"
},
{
"path": "db/migrate/20160415043427_create_slack_notifications.rb",
"chars": 402,
"preview": "class CreateSlackNotifications < ActiveRecord::Migration[5.0]\n def change\n create_table :slack_notifications, option"
},
{
"path": "db/migrate/20160509041452_add_job_queue_id_to_job_executions.rb",
"chars": 145,
"preview": "class AddJobQueueIdToJobExecutions < ActiveRecord::Migration[5.0]\n def change\n add_column :job_executions, :job_queu"
},
{
"path": "db/migrate/20160516041710_create_job_retries.rb",
"chars": 433,
"preview": "class CreateJobRetries < ActiveRecord::Migration[5.0]\n def change\n create_table :job_retries, options: 'ENGINE=InnoD"
},
{
"path": "db/migrate/20160829023237_prefix_barbeque_to_tables.rb",
"chars": 415,
"preview": "class PrefixBarbequeToTables < ActiveRecord::Migration[5.0]\n def change\n rename_table :apps, :barbeque_apps\n rena"
},
{
"path": "db/migrate/20170420030157_create_barbeque_sns_subscriptions.rb",
"chars": 301,
"preview": "class CreateBarbequeSnsSubscriptions < ActiveRecord::Migration[5.0]\n def change\n create_table :barbeque_sns_subscrip"
},
{
"path": "db/migrate/20170711085157_create_barbeque_docker_containers.rb",
"chars": 366,
"preview": "class CreateBarbequeDockerContainers < ActiveRecord::Migration[5.0]\n def change\n create_table :barbeque_docker_conta"
},
{
"path": "db/migrate/20170712075449_create_barbeque_ecs_hako_tasks.rb",
"chars": 392,
"preview": "class CreateBarbequeEcsHakoTasks < ActiveRecord::Migration[5.0]\n def change\n create_table :barbeque_ecs_hako_tasks, "
},
{
"path": "db/migrate/20170724025542_add_index_to_job_execution_status.rb",
"chars": 182,
"preview": "class AddIndexToJobExecutionStatus < ActiveRecord::Migration[5.0]\n def change\n add_index :barbeque_job_executions, :"
},
{
"path": "db/migrate/20180411070937_add_index_to_barbeque_job_executions_created_at.rb",
"chars": 153,
"preview": "class AddIndexToBarbequeJobExecutionsCreatedAt < ActiveRecord::Migration[5.1]\n def change\n add_index :barbeque_job_e"
},
{
"path": "db/migrate/20190221050714_create_barbeque_retry_configs.rb",
"chars": 517,
"preview": "class CreateBarbequeRetryConfigs < ActiveRecord::Migration[5.2]\n def change\n create_table :barbeque_retry_configs, o"
},
{
"path": "db/migrate/20190311034445_add_notify_failure_only_if_retry_limit_reached_to_barbeque_slack_notifications.rb",
"chars": 257,
"preview": "class AddNotifyFailureOnlyIfRetryLimitReachedToBarbequeSlackNotifications < ActiveRecord::Migration[5.2]\n def change\n "
},
{
"path": "db/migrate/20190315052951_change_barbeque_retry_configs_base_delay_default_value.rb",
"chars": 257,
"preview": "class ChangeBarbequeRetryConfigsBaseDelayDefaultValue < ActiveRecord::Migration[5.2]\n def up\n change_column_default "
},
{
"path": "db/migrate/20191029105530_change_job_name_to_case_sensitive.rb",
"chars": 263,
"preview": "class ChangeJobNameToCaseSensitive < ActiveRecord::Migration[5.2]\n def up\n change_column :barbeque_job_definitions, "
},
{
"path": "db/migrate/20240415080757_fix_collations.rb",
"chars": 2220,
"preview": "# In MySQL 8.0, the default collation for utf8mb4 is changed to utf8mb4_0900_ai_ci.\n# The column collations are utf8mb4_"
},
{
"path": "doc/api/job_executions.md",
"chars": 4820,
"preview": "## GET /v1/job_executions/:message_id\nShows a status of a job_execution.\n\n### Example\n\n#### Request\n```\nGET /v1/job_exec"
},
{
"path": "doc/api/job_retries.md",
"chars": 727,
"preview": "## POST /v1/job_executions/:job_execution_message_id/retries\nEnqueues a message to retry a specified message.\n\n### Param"
},
{
"path": "doc/api/revision_locks.md",
"chars": 1098,
"preview": "## POST /v1/apps/:app_id/revision_lock\nUpdates a tag of docker_image.\n\n### Parameters\n* `revision` string (required) - D"
},
{
"path": "doc/toc.md",
"chars": 1024,
"preview": "## Table of Contents\n* [api/job_executions.md](api/job_executions.md)\n * [GET /v1/job_executions/:message_id](api/job_ex"
},
{
"path": "lib/barbeque/config.rb",
"chars": 1191,
"preview": "require 'erb'\nrequire 'yaml'\n\nmodule Barbeque\n class Config\n attr_accessor :exception_handler, :executor, :executor_"
},
{
"path": "lib/barbeque/docker_image.rb",
"chars": 585,
"preview": "module Barbeque\n class DockerImage\n DEFAULT_TAG = 'latest'\n\n def initialize(str)\n # See: https://github.com/"
},
{
"path": "lib/barbeque/engine.rb",
"chars": 489,
"preview": "# Gems used by Barbeque::Engine\nrequire 'rinku'\n\nmodule Barbeque\n class Engine < ::Rails::Engine\n isolate_namespace "
},
{
"path": "lib/barbeque/exception_handler.rb",
"chars": 1965,
"preview": "require 'barbeque/config'\n\nmodule Barbeque\n module ExceptionHandler\n class << self\n delegate :clear_context, :s"
},
{
"path": "lib/barbeque/execution_log.rb",
"chars": 2797,
"preview": "require 'aws-sdk-s3'\nrequire 'active_support'\nrequire 'active_support/core_ext'\nrequire 'barbeque/exception_handler'\n\nmo"
},
{
"path": "lib/barbeque/execution_poller.rb",
"chars": 870,
"preview": "require 'barbeque/exception_handler'\nrequire 'barbeque/executor'\n\nmodule Barbeque\n class ExecutionPoller\n def initia"
},
{
"path": "lib/barbeque/executor/docker.rb",
"chars": 5618,
"preview": "require 'barbeque/docker_image'\nrequire 'barbeque/execution_log'\nrequire 'barbeque/slack_notifier'\nrequire 'open3'\n\nmodu"
},
{
"path": "lib/barbeque/executor/hako.rb",
"chars": 6587,
"preview": "require 'barbeque/docker_image'\nrequire 'barbeque/execution_log'\nrequire 'barbeque/hako_s3_client'\nrequire 'barbeque/sla"
},
{
"path": "lib/barbeque/executor.rb",
"chars": 1103,
"preview": "require 'barbeque/config'\nrequire 'barbeque/executor/docker'\nrequire 'barbeque/executor/hako'\n\nmodule Barbeque\n # Execu"
},
{
"path": "lib/barbeque/hako_s3_client.rb",
"chars": 1276,
"preview": "require 'uri'\nrequire 'aws-sdk-ecs'\nrequire 'aws-sdk-s3'\n\nmodule Barbeque\n class HakoS3Client\n # @param [String] one"
},
{
"path": "lib/barbeque/maintenance.rb",
"chars": 209,
"preview": "module Barbeque\n module Maintenance\n def self.database_maintenance_mode?\n ENV['BARBEQUE_DATABASE_MAINTENANCE'] "
},
{
"path": "lib/barbeque/message/base.rb",
"chars": 1064,
"preview": "module Barbeque\n module Message\n # A model wrapping Aws::SQS::Types::Message.\n class Base\n attr_reader :id "
},
{
"path": "lib/barbeque/message/invalid_message.rb",
"chars": 157,
"preview": "require 'barbeque/message/base'\n\nmodule Barbeque\n module Message\n class InvalidMessage < Base\n def valid?\n "
},
{
"path": "lib/barbeque/message/job_execution.rb",
"chars": 531,
"preview": "require 'barbeque/message/base'\n\nmodule Barbeque\n module Message\n class JobExecution < Base\n attr_reader :body "
},
{
"path": "lib/barbeque/message/job_retry.rb",
"chars": 317,
"preview": "require 'barbeque/message/base'\n\nmodule Barbeque\n module Message\n class JobRetry < Base\n attr_reader :retry_mes"
},
{
"path": "lib/barbeque/message/notification.rb",
"chars": 855,
"preview": "require 'barbeque/message/base'\n\nmodule Barbeque\n module Message\n class Notification < Base\n attr_reader :body "
},
{
"path": "lib/barbeque/message.rb",
"chars": 870,
"preview": "require 'barbeque/exception_handler'\nrequire 'barbeque/message/base'\nrequire 'barbeque/message/invalid_message'\nrequire "
},
{
"path": "lib/barbeque/message_handler/job_execution.rb",
"chars": 2238,
"preview": "require 'barbeque/execution_log'\nrequire 'barbeque/executor'\nrequire 'barbeque/slack_notifier'\n\nmodule Barbeque\n module"
},
{
"path": "lib/barbeque/message_handler/job_retry.rb",
"chars": 2047,
"preview": "require 'barbeque/execution_log'\nrequire 'barbeque/executor'\nrequire 'barbeque/slack_notifier'\n\nmodule Barbeque\n module"
},
{
"path": "lib/barbeque/message_handler/notification.rb",
"chars": 563,
"preview": "require 'barbeque/message_handler/job_execution'\n\nmodule Barbeque\n module MessageHandler\n class Notification < JobEx"
},
{
"path": "lib/barbeque/message_handler.rb",
"chars": 244,
"preview": "module Barbeque\n module MessageHandler\n class DuplicatedExecution < StandardError; end\n end\nend\n\nrequire 'barbeque/"
},
{
"path": "lib/barbeque/message_queue.rb",
"chars": 1342,
"preview": "require 'aws-sdk-sqs'\nrequire 'barbeque/config'\nrequire 'barbeque/message'\n\nmodule Barbeque\n class MessageQueue\n att"
},
{
"path": "lib/barbeque/retry_poller.rb",
"chars": 938,
"preview": "require 'barbeque/exception_handler'\nrequire 'barbeque/executor'\n\nmodule Barbeque\n class RetryPoller\n def initialize"
},
{
"path": "lib/barbeque/runner.rb",
"chars": 1625,
"preview": "require 'barbeque/config'\nrequire 'barbeque/exception_handler'\nrequire 'barbeque/execution_log'\nrequire 'barbeque/messag"
},
{
"path": "lib/barbeque/slack_client.rb",
"chars": 856,
"preview": "require 'net/http'\nrequire 'json'\nrequire 'uri'\n\nmodule Barbeque\n class SlackClient\n def initialize(channel)\n @"
},
{
"path": "lib/barbeque/slack_notifier.rb",
"chars": 3269,
"preview": "require 'barbeque/slack_client'\n\nmodule Barbeque\n module SlackNotifier\n class << self\n # @param [Barbeque::JobE"
},
{
"path": "lib/barbeque/version.rb",
"chars": 41,
"preview": "module Barbeque\n VERSION = '2.10.0'\nend\n"
},
{
"path": "lib/barbeque/worker.rb",
"chars": 1649,
"preview": "require 'barbeque/exception_handler'\nrequire 'barbeque/execution_poller'\nrequire 'barbeque/retry_poller'\nrequire 'barbeq"
},
{
"path": "lib/barbeque.rb",
"chars": 91,
"preview": "require 'barbeque/docker_image'\nrequire 'barbeque/engine'\nrequire 'barbeque/execution_log'\n"
},
{
"path": "lib/tasks/barbeque_tasks.rake",
"chars": 602,
"preview": "namespace :barbeque do\n desc 'Start worker to execute jobs'\n task worker: :environment do\n ENV['BARBEQUE_QUEUE'] ||"
},
{
"path": "spec/barbeque/config_builder_spec.rb",
"chars": 681,
"preview": "require 'rails_helper'\nrequire 'barbeque/config'\n\ndescribe Barbeque::ConfigBuilder do\n describe '#build_config' do\n "
},
{
"path": "spec/barbeque/config_spec.rb",
"chars": 380,
"preview": "require 'rails_helper'\nrequire 'barbeque/config'\n\ndescribe Barbeque::Config do\n describe '#executor_options' do\n it "
},
{
"path": "spec/barbeque/docker_image_spec.rb",
"chars": 1305,
"preview": "require 'barbeque'\n\ndescribe Barbeque::DockerImage do\n it 'parses docker image name without a tag' do\n image = Barbe"
},
{
"path": "spec/barbeque/exception_handler_spec.rb",
"chars": 682,
"preview": "require 'rails_helper'\nrequire 'barbeque/exception_handler'\n\ndescribe Barbeque::ExceptionHandler do\n describe '.handle_"
},
{
"path": "spec/barbeque/execution_log_spec.rb",
"chars": 3093,
"preview": "require 'rails_helper'\nrequire 'barbeque/execution_log'\n\ndescribe Barbeque::ExecutionLog do\n let(:job_execution) { crea"
},
{
"path": "spec/barbeque/execution_poller_spec.rb",
"chars": 697,
"preview": "require 'rails_helper'\nrequire 'barbeque/execution_poller'\n\nRSpec.describe Barbeque::ExecutionPoller do\n let(:job_queue"
},
{
"path": "spec/barbeque/executor/docker_spec.rb",
"chars": 22730,
"preview": "require 'rails_helper'\nrequire 'barbeque/executor/docker'\n\nRSpec.describe Barbeque::Executor::Docker do\n let(:executor)"
},
{
"path": "spec/barbeque/executor/hako_spec.rb",
"chars": 21581,
"preview": "require 'rails_helper'\nrequire 'barbeque/executor/hako'\n\ndescribe Barbeque::Executor::Hako do\n let(:hako_directory) { '"
},
{
"path": "spec/barbeque/executor_spec.rb",
"chars": 1132,
"preview": "require 'rails_helper'\nrequire 'barbeque/executor'\n\ndescribe Barbeque::Executor do\n describe '.create' do\n before do"
},
{
"path": "spec/barbeque/message_handler/job_execution_spec.rb",
"chars": 4632,
"preview": "require 'rails_helper'\nrequire 'barbeque/worker'\n\ndescribe Barbeque::MessageHandler::JobExecution do\n describe '#run' d"
},
{
"path": "spec/barbeque/message_handler/job_retry_spec.rb",
"chars": 4623,
"preview": "require 'rails_helper'\nrequire 'barbeque/worker'\n\ndescribe Barbeque::MessageHandler::JobRetry do\n describe '#run' do\n "
},
{
"path": "spec/barbeque/message_handler/notification_spec.rb",
"chars": 898,
"preview": "require 'rails_helper'\nrequire 'barbeque/worker'\n\ndescribe Barbeque::MessageHandler::Notification do\n describe '#initia"
},
{
"path": "spec/barbeque/message_queue_spec.rb",
"chars": 1333,
"preview": "require 'rails_helper'\nrequire 'barbeque/worker'\n\ndescribe Barbeque::MessageQueue do\n describe '#dequeue' do\n let(:j"
},
{
"path": "spec/barbeque/message_spec.rb",
"chars": 3042,
"preview": "require 'barbeque/worker'\nrequire 'rails_helper'\n\ndescribe Barbeque::Message::Base do\n let(:application) { 'cookpad' }\n"
},
{
"path": "spec/barbeque/retry_poller_spec.rb",
"chars": 758,
"preview": "require 'rails_helper'\nrequire 'barbeque/retry_poller'\n\nRSpec.describe Barbeque::RetryPoller do\n let(:job_queue) { crea"
},
{
"path": "spec/barbeque/runner_spec.rb",
"chars": 2553,
"preview": "require 'rails_helper'\nrequire 'barbeque/runner'\n\nRSpec.describe Barbeque::Runner do\n let(:job_queue) { create(:job_que"
},
{
"path": "spec/barbeque/worker_spec.rb",
"chars": 3649,
"preview": "require 'rails_helper'\nrequire 'barbeque/worker'\n\ndescribe Barbeque::Worker do\n let!(:job_execution) { create(:job_exec"
},
{
"path": "spec/controllers/barbeque/apps_controller_spec.rb",
"chars": 2219,
"preview": "require 'rails_helper'\n\ndescribe Barbeque::AppsController do\n routes { Barbeque::Engine.routes }\n\n describe '#index' d"
},
{
"path": "spec/controllers/barbeque/job_definitions_controller_spec.rb",
"chars": 8565,
"preview": "require 'rails_helper'\n\ndescribe Barbeque::JobDefinitionsController do\n routes { Barbeque::Engine.routes }\n\n describe "
},
{
"path": "spec/controllers/barbeque/job_executions_controller_spec.rb",
"chars": 2947,
"preview": "require 'rails_helper'\nrequire 'barbeque/execution_log'\n\ndescribe Barbeque::JobExecutionsController do\n routes { Barbeq"
},
{
"path": "spec/controllers/barbeque/job_queues_controller_spec.rb",
"chars": 3589,
"preview": "require 'rails_helper'\n\ndescribe Barbeque::JobQueuesController do\n routes { Barbeque::Engine.routes }\n\n describe '#ind"
},
{
"path": "spec/controllers/barbeque/job_retries_controller_spec.rb",
"chars": 1550,
"preview": "require 'rails_helper'\nrequire 'barbeque/execution_log'\n\ndescribe Barbeque::JobRetriesController do\n routes { Barbeque:"
},
{
"path": "spec/controllers/barbeque/sns_subscriptions_controller_spec.rb",
"chars": 5523,
"preview": "require 'rails_helper'\n\ndescribe Barbeque::SnsSubscriptionsController do\n routes { Barbeque::Engine.routes }\n\n before "
},
{
"path": "spec/dummy/.gitignore",
"chars": 468,
"preview": "# See https://help.github.com/articles/ignoring-files for more about ignoring files.\n#\n# If you find yourself ignoring t"
},
{
"path": "spec/dummy/Rakefile",
"chars": 227,
"preview": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they wil"
},
{
"path": "spec/dummy/app/assets/config/manifest.js",
"chars": 101,
"preview": "//= link_tree ../images\n//= link_directory ../javascripts .js\n//= link_directory ../stylesheets .css\n"
},
{
"path": "spec/dummy/app/assets/images/.keep",
"chars": 0,
"preview": ""
},
{
"path": "spec/dummy/app/assets/javascripts/application.js",
"chars": 712,
"preview": "// This is a manifest file that'll be compiled into application.js, which will include all the files\n// listed below.\n//"
},
{
"path": "spec/dummy/app/assets/javascripts/channels/.keep",
"chars": 0,
"preview": ""
},
{
"path": "spec/dummy/app/assets/stylesheets/application.css",
"chars": 736,
"preview": "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below"
},
{
"path": "spec/dummy/app/controllers/application_controller.rb",
"chars": 97,
"preview": "class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\nend\n"
},
{
"path": "spec/dummy/app/controllers/concerns/.keep",
"chars": 0,
"preview": ""
},
{
"path": "spec/dummy/app/helpers/application_helper.rb",
"chars": 29,
"preview": "module ApplicationHelper\nend\n"
},
{
"path": "spec/dummy/app/models/concerns/.keep",
"chars": 0,
"preview": ""
},
{
"path": "spec/dummy/app/views/layouts/application.html.erb",
"chars": 249,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <title>Dummy</title>\n <%= csrf_meta_tags %>\n\n <%= stylesheet_link_tag 'appl"
},
{
"path": "spec/dummy/app/views/layouts/mailer.html.erb",
"chars": 229,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n <style>\n "
},
{
"path": "spec/dummy/app/views/layouts/mailer.text.erb",
"chars": 13,
"preview": "<%= yield %>\n"
},
{
"path": "spec/dummy/bin/bundle",
"chars": 125,
"preview": "#!/usr/bin/env ruby\nENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)\nload Gem.bin_path('bundler', 'bund"
},
{
"path": "spec/dummy/bin/rails",
"chars": 141,
"preview": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequ"
},
{
"path": "spec/dummy/bin/rake",
"chars": 90,
"preview": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
},
{
"path": "spec/dummy/bin/setup",
"chars": 1010,
"preview": "#!/usr/bin/env ruby\nrequire \"fileutils\"\n\n# path to your application root.\nAPP_ROOT = File.expand_path(\"..\", __dir__)\n\nde"
},
{
"path": "spec/dummy/bin/spring",
"chars": 488,
"preview": "#!/usr/bin/env ruby\n\n# This file loads spring without using Bundler, in order to be fast.\n# It gets overwritten when you"
},
{
"path": "spec/dummy/bin/update",
"chars": 819,
"preview": "#!/usr/bin/env ruby\nrequire 'fileutils'\ninclude FileUtils\n\n# path to your application root.\nAPP_ROOT = File.expand_path("
},
{
"path": "spec/dummy/bin/yarn",
"chars": 303,
"preview": "#!/usr/bin/env ruby\nAPP_ROOT = File.expand_path('..', __dir__)\nDir.chdir(APP_ROOT) do\n begin\n exec \"yarnpkg\", *ARGV\n"
},
{
"path": "spec/dummy/config/application.rb",
"chars": 1173,
"preview": "require_relative \"boot\"\n\nrequire \"rails\"\n# Pick the frameworks you want:\nrequire \"active_model/railtie\"\n# require \"activ"
},
{
"path": "spec/dummy/config/barbeque.empty.yml",
"chars": 9,
"preview": "test: {}\n"
},
{
"path": "spec/dummy/config/barbeque.erb.yml",
"chars": 39,
"preview": "test:\n executor: <%= 'Foo' + 'Bar' %>\n"
},
{
"path": "spec/dummy/config/barbeque.hako.yml",
"chars": 322,
"preview": "default: &default\n executor: Hako\n executor_options:\n hako_dir: /home/k0kubun/hako_repo\n hako_env:\n ACCESS_"
},
{
"path": "spec/dummy/config/barbeque.yml",
"chars": 149,
"preview": "default: &default\n exception_handler: RailsLogger\n executor: Docker\n\ndevelopment:\n <<: *default\n\ntest:\n <<: *default"
},
{
"path": "spec/dummy/config/boot.rb",
"chars": 128,
"preview": "ENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../Gemfile\", __dir__)\n\nrequire \"bundler/setup\" # Set up gems listed in the G"
},
{
"path": "spec/dummy/config/database.yml",
"chars": 1590,
"preview": "# MySQL. Versions 5.0 and up are supported.\n#\n# Install the MySQL driver\n# gem install mysql2\n#\n# Ensure the MySQL gem"
},
{
"path": "spec/dummy/config/environment.rb",
"chars": 128,
"preview": "# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.init"
},
{
"path": "spec/dummy/config/environments/development.rb",
"chars": 2137,
"preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n # Settings specified here will take pre"
},
{
"path": "spec/dummy/config/environments/production.rb",
"chars": 3038,
"preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n # Settings specified here will take pre"
},
{
"path": "spec/dummy/config/environments/test.rb",
"chars": 2008,
"preview": "require \"active_support/core_ext/integer/time\"\n\n# The test environment is used exclusively to run your application's\n# t"
}
]
// ... and 53 more files (download for full content)
About this extraction
This page contains the full source code of the cookpad/barbeque GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 253 files (2.5 MB), approximately 660.6k tokens, and a symbol index with 1455 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.