├── examples
├── myapp
│ ├── log
│ │ └── .keep
│ ├── storage
│ │ └── .keep
│ ├── tmp
│ │ └── .keep
│ ├── vendor
│ │ └── .keep
│ ├── lib
│ │ ├── assets
│ │ │ └── .keep
│ │ └── tasks
│ │ │ └── .keep
│ ├── public
│ │ ├── favicon.ico
│ │ ├── apple-touch-icon.png
│ │ ├── apple-touch-icon-precomposed.png
│ │ ├── robots.txt
│ │ ├── 500.html
│ │ ├── 422.html
│ │ └── 404.html
│ ├── test
│ │ ├── helpers
│ │ │ └── .keep
│ │ ├── mailers
│ │ │ └── .keep
│ │ ├── models
│ │ │ ├── .keep
│ │ │ └── task_test.rb
│ │ ├── system
│ │ │ ├── .keep
│ │ │ └── tasks_test.rb
│ │ ├── controllers
│ │ │ ├── .keep
│ │ │ ├── import_tasks_controller_test.rb
│ │ │ └── tasks_controller_test.rb
│ │ ├── fixtures
│ │ │ ├── .keep
│ │ │ ├── files
│ │ │ │ └── .keep
│ │ │ └── tasks.yml
│ │ ├── integration
│ │ │ └── .keep
│ │ ├── application_system_test_case.rb
│ │ └── test_helper.rb
│ ├── app
│ │ ├── assets
│ │ │ ├── images
│ │ │ │ └── .keep
│ │ │ ├── javascripts
│ │ │ │ ├── channels
│ │ │ │ │ └── .keep
│ │ │ │ ├── tasks.coffee
│ │ │ │ ├── import_tasks.coffee
│ │ │ │ ├── cable.js
│ │ │ │ └── application.js
│ │ │ ├── config
│ │ │ │ └── manifest.js
│ │ │ └── stylesheets
│ │ │ │ ├── tasks.scss
│ │ │ │ ├── import_tasks.scss
│ │ │ │ ├── application.css
│ │ │ │ └── scaffolds.scss
│ │ ├── models
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ ├── task.rb
│ │ │ └── application_record.rb
│ │ ├── controllers
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ ├── application_controller.rb
│ │ │ ├── import_tasks_controller.rb
│ │ │ └── tasks_controller.rb
│ │ ├── views
│ │ │ ├── layouts
│ │ │ │ ├── mailer.text.erb
│ │ │ │ ├── mailer.html.erb
│ │ │ │ └── application.html.erb
│ │ │ ├── tasks
│ │ │ │ ├── show.json.jbuilder
│ │ │ │ ├── index.json.jbuilder
│ │ │ │ ├── new.html.erb
│ │ │ │ ├── _task.json.jbuilder
│ │ │ │ ├── edit.html.erb
│ │ │ │ ├── show.html.erb
│ │ │ │ ├── _import_form.html.erb
│ │ │ │ ├── _form.html.erb
│ │ │ │ └── index.html.erb
│ │ │ └── import_tasks
│ │ │ │ └── create.html.erb
│ │ ├── helpers
│ │ │ ├── tasks_helper.rb
│ │ │ ├── application_helper.rb
│ │ │ └── import_tasks_helper.rb
│ │ ├── jobs
│ │ │ ├── application_job.rb
│ │ │ ├── print_message_job.rb
│ │ │ ├── compute_pi_job.rb
│ │ │ ├── import_task_job.rb
│ │ │ └── archive_done_tasks_job.rb
│ │ ├── channels
│ │ │ └── application_cable
│ │ │ │ ├── channel.rb
│ │ │ │ └── connection.rb
│ │ └── mailers
│ │ │ └── application_mailer.rb
│ ├── package.json
│ ├── yarn.lock
│ ├── bin
│ │ ├── bundle
│ │ ├── rake
│ │ ├── rails
│ │ ├── yarn
│ │ ├── spring
│ │ ├── update
│ │ └── setup
│ ├── config
│ │ ├── spring.rb
│ │ ├── environment.rb
│ │ ├── routes.rb
│ │ ├── initializers
│ │ │ ├── mime_types.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ ├── kube_queue.rb
│ │ │ ├── application_controller_renderer.rb
│ │ │ ├── cookies_serializer.rb
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── wrap_parameters.rb
│ │ │ ├── assets.rb
│ │ │ ├── inflections.rb
│ │ │ └── content_security_policy.rb
│ │ ├── boot.rb
│ │ ├── cable.yml
│ │ ├── database.yml
│ │ ├── credentials.yml.enc
│ │ ├── application.rb
│ │ ├── locales
│ │ │ └── en.yml
│ │ ├── storage.yml
│ │ ├── puma.rb
│ │ └── environments
│ │ │ ├── test.rb
│ │ │ ├── development.rb
│ │ │ └── production.rb
│ ├── config.ru
│ ├── Rakefile
│ ├── db
│ │ ├── migrate
│ │ │ └── 20190813062954_create_tasks.rb
│ │ ├── seeds.rb
│ │ └── schema.rb
│ ├── README.md
│ ├── .gitignore
│ ├── Gemfile
│ ├── k8s
│ │ ├── database.yaml
│ │ └── app.yaml
│ ├── tasks.csv
│ └── Gemfile.lock
├── docker
│ ├── Gemfile
│ └── test_worker.rb
└── k8s
│ └── service-account.yaml
├── .dockerignore
├── gemfiles
├── .bundle
│ └── config
├── rails_5.1.gemfile
├── rails_5.2.gemfile
├── rails_6.0.gemfile
├── rails_5.1.gemfile.lock
├── rails_5.2.gemfile.lock
└── rails_6.0.gemfile.lock
├── .rspec
├── .gcloudignore
├── lib
├── kube_queue
│ ├── version.rb
│ ├── railties.rb
│ ├── configuration.rb
│ ├── executor.rb
│ ├── manifest_builder.rb
│ ├── client.rb
│ ├── worker
│ │ └── dsl.rb
│ ├── job_specification.rb
│ └── worker.rb
├── active_job
│ └── adapters
│ │ └── kube_queue_adapter.rb
└── kube_queue.rb
├── Gemfile
├── Rakefile
├── bin
├── setup
└── console
├── .gitignore
├── Appraisals
├── CHANGELOG.md
├── cloudbuild.yaml
├── spec
├── kube_queue_spec.rb
├── spec_helper.rb
├── workers.rb
├── template
│ └── test.yaml
├── active_job
│ └── adapters
│ │ └── kube_queue_adapter_spec.rb
└── kube_queue
│ ├── manifest_spec.rb
│ └── worker_spec.rb
├── Guardfile
├── Dockerfile
├── Dockerfile.myapp
├── LICENSE.txt
├── .rubocop.yml
├── .travis.yml
├── kube_queue.gemspec
├── exe
└── kube_queue
├── template
└── job.yaml
├── CODE_OF_CONDUCT.md
└── README.md
/examples/myapp/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/storage/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/tmp/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/vendor/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/lib/tasks/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/test/helpers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/test/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/test/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/test/system/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/test/controllers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/test/fixtures/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/test/integration/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | secrets
2 | Gemfile.lock
3 |
--------------------------------------------------------------------------------
/examples/myapp/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gemfiles/.bundle/config:
--------------------------------------------------------------------------------
1 | ---
2 | BUNDLE_RETRY: "1"
3 |
--------------------------------------------------------------------------------
/examples/myapp/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format documentation
2 | --color
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/examples/myapp/app/helpers/tasks_helper.rb:
--------------------------------------------------------------------------------
1 | module TasksHelper
2 | end
3 |
--------------------------------------------------------------------------------
/.gcloudignore:
--------------------------------------------------------------------------------
1 | !.git
2 | secrets
3 | examples/myapp/tmp/*
4 | examples/myapp/log/*
5 |
--------------------------------------------------------------------------------
/examples/myapp/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/examples/myapp/app/helpers/import_tasks_helper.rb:
--------------------------------------------------------------------------------
1 | module ImportTasksHelper
2 | end
3 |
--------------------------------------------------------------------------------
/lib/kube_queue/version.rb:
--------------------------------------------------------------------------------
1 | module KubeQueue
2 | VERSION = "0.4.1".freeze
3 | end
4 |
--------------------------------------------------------------------------------
/examples/myapp/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/tasks/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "tasks/task", task: @task
2 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/tasks/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @tasks, partial: "tasks/task", as: :task
2 |
--------------------------------------------------------------------------------
/examples/docker/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem 'kube_queue', path: '/vendor/kube_queue'
4 |
--------------------------------------------------------------------------------
/examples/myapp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "myapp",
3 | "private": true,
4 | "dependencies": {}
5 | }
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | # Specify your gem's dependencies in kube_queue.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/examples/myapp/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | end
3 |
--------------------------------------------------------------------------------
/examples/myapp/app/models/task.rb:
--------------------------------------------------------------------------------
1 | class Task < ApplicationRecord
2 | enum state: { todo: 0, doing: 1, done: 2 }
3 | end
4 |
--------------------------------------------------------------------------------
/examples/myapp/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/kube_queue/railties.rb:
--------------------------------------------------------------------------------
1 | ActiveSupport.on_load(:active_job) do
2 | require "active_job/adapters/kube_queue_adapter"
3 | end
4 |
--------------------------------------------------------------------------------
/examples/myapp/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/examples/myapp/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/import_tasks/create.html.erb:
--------------------------------------------------------------------------------
1 |
ImportTasks#create
2 | Find me in app/views/import_tasks/create.html.erb
3 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rspec/core/rake_task"
3 |
4 | RSpec::Core::RakeTask.new(:spec)
5 |
6 | task :default => :spec
7 |
--------------------------------------------------------------------------------
/examples/myapp/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/tasks/new.html.erb:
--------------------------------------------------------------------------------
1 | New Task
2 |
3 | <%= render 'form', task: @task %>
4 |
5 | <%= link_to 'Back', tasks_path %>
6 |
--------------------------------------------------------------------------------
/lib/kube_queue/configuration.rb:
--------------------------------------------------------------------------------
1 | module KubeQueue
2 | class Configuration
3 | def configure
4 | yield self
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/examples/myapp/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/examples/myapp/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/tasks/_task.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! task, :id, :state, :name, :created_at, :updated_at
2 | json.url task_url(task, format: :json)
3 |
--------------------------------------------------------------------------------
/examples/myapp/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/examples/myapp/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/examples/myapp/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w[
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ].each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/gemfiles/rails_5.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "activejob", "5.1.7"
6 |
7 | gemspec path: "../"
8 |
--------------------------------------------------------------------------------
/gemfiles/rails_5.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "activejob", "5.2.3"
6 |
7 | gemspec path: "../"
8 |
--------------------------------------------------------------------------------
/examples/myapp/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/gemfiles/rails_6.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "activejob", "6.0.0.beta2"
6 |
7 | gemspec path: "../"
8 |
--------------------------------------------------------------------------------
/examples/myapp/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/examples/myapp/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | root to: 'tasks#index'
3 |
4 | resources :tasks
5 | post 'task/import', to: 'import_tasks#create'
6 | end
7 |
--------------------------------------------------------------------------------
/examples/myapp/test/models/task_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class TaskTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/tasks/edit.html.erb:
--------------------------------------------------------------------------------
1 | Editing Task
2 |
3 | <%= render 'form', task: @task %>
4 |
5 | <%= link_to 'Show', @task %> |
6 | <%= link_to 'Back', tasks_path %>
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /secrets
4 | /_yardoc/
5 | /Gemfile.lock
6 | /coverage/
7 | /doc/
8 | /pkg/
9 | /spec/reports/
10 | /tmp/
11 |
12 | # rspec failure tracking
13 | .rspec_status
14 |
--------------------------------------------------------------------------------
/examples/myapp/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/examples/myapp/test/application_system_test_case.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
5 | end
6 |
--------------------------------------------------------------------------------
/examples/myapp/app/assets/stylesheets/tasks.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the Tasks controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | appraise "rails-5.1" do
2 | gem "activejob", "5.1.7"
3 | end
4 |
5 | appraise "rails-5.2" do
6 | gem "activejob", "5.2.3"
7 | end
8 |
9 | appraise "rails-6.0" do
10 | gem "activejob", "6.0.0.beta2"
11 | end
12 |
--------------------------------------------------------------------------------
/examples/myapp/test/fixtures/tasks.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | state: 1
5 | name: MyString
6 |
7 | two:
8 | state: 1
9 | name: MyString
10 |
--------------------------------------------------------------------------------
/examples/myapp/app/assets/stylesheets/import_tasks.scss:
--------------------------------------------------------------------------------
1 | // Place all the styles related to the ImportTasks controller here.
2 | // They will automatically be included in application.css.
3 | // You can use Sass (SCSS) here: http://sass-lang.com/
4 |
--------------------------------------------------------------------------------
/examples/myapp/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
5 |
--------------------------------------------------------------------------------
/examples/myapp/app/assets/javascripts/tasks.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/examples/myapp/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: myapp_production
11 |
--------------------------------------------------------------------------------
/examples/myapp/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/examples/myapp/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/examples/myapp/app/assets/javascripts/import_tasks.coffee:
--------------------------------------------------------------------------------
1 | # Place all the behaviors and hooks related to the matching controller here.
2 | # All this logic will automatically be available in application.js.
3 | # You can use CoffeeScript in this file: http://coffeescript.org/
4 |
--------------------------------------------------------------------------------
/examples/myapp/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require_relative '../config/boot'
8 | require 'rake'
9 | Rake.application.run
10 |
--------------------------------------------------------------------------------
/examples/myapp/test/controllers/import_tasks_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class ImportTasksControllerTest < ActionDispatch::IntegrationTest
4 | test "should get create" do
5 | get import_tasks_create_url
6 | assert_response :success
7 | end
8 |
9 | end
10 |
--------------------------------------------------------------------------------
/examples/myapp/config/initializers/kube_queue.rb:
--------------------------------------------------------------------------------
1 | KubeQueue.kubernetes_configure do |client|
2 | client.url = ENV['K8S_URL']
3 | client.ssl_ca_file = ENV['K8S_CA_CERT_FILE']
4 | client.auth_token = File.read(ENV['K8S_BEARER_TOKEN_FILE']) if File.exists?(ENV['K8S_BEARER_TOKEN_FILE'].to_s)
5 | end
6 |
--------------------------------------------------------------------------------
/examples/myapp/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/examples/myapp/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/examples/myapp/db/migrate/20190813062954_create_tasks.rb:
--------------------------------------------------------------------------------
1 | class CreateTasks < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :tasks do |t|
4 | t.integer :state, default: 0, null: false
5 | t.string :name, null: false
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/examples/myapp/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../config/application', __dir__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/tasks/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= notice %>
2 |
3 |
4 | State:
5 | <%= @task.state %>
6 |
7 |
8 |
9 | Name:
10 | <%= @task.name %>
11 |
12 |
13 | <%= link_to 'Edit', edit_task_path(@task) %> |
14 | <%= link_to 'Back', tasks_path %>
15 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/tasks/_import_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_with(url: task_import_path, local: true) do |form| %>
2 |
3 | <%= form.label :upload_file %>
4 | <%= form.file_field :upload_file %>
5 |
6 |
7 |
8 | <%= form.submit 'import CSV' %>
9 |
10 | <% end %>
11 |
--------------------------------------------------------------------------------
/examples/myapp/app/jobs/print_message_job.rb:
--------------------------------------------------------------------------------
1 | class PrintMessageJob < ApplicationJob
2 | include KubeQueue::Worker
3 |
4 | worker_name 'print-message'
5 | image "gcr.io/#{ENV['PROJECT_ID']}/kube-queue-test-app"
6 | container_name 'kube-queue-test-app'
7 |
8 | def perform(payload)
9 | logger.info payload[:message]
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/examples/myapp/app/jobs/compute_pi_job.rb:
--------------------------------------------------------------------------------
1 | class ComputePiJob < ApplicationJob
2 | include KubeQueue::Worker
3 |
4 | worker_name 'pi'
5 | image 'perl'
6 | container_name 'pi'
7 | command "perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"
8 |
9 | cpu_limit '0.3'
10 | cpu_request '0.2'
11 | memory_limit '100m'
12 | memory_request '50m'
13 | end
14 |
--------------------------------------------------------------------------------
/examples/myapp/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | require_relative '../config/environment'
3 | require 'rails/test_help'
4 |
5 | class ActiveSupport::TestCase
6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
7 | fixtures :all
8 |
9 | # Add more helper methods to be used by all tests here...
10 | end
11 |
--------------------------------------------------------------------------------
/examples/myapp/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path('..', __dir__)
3 | Dir.chdir(APP_ROOT) do
4 | begin
5 | exec "yarnpkg", *ARGV
6 | rescue Errno::ENOENT
7 | $stderr.puts "Yarn executable was not detected in the system."
8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
9 | exit 1
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/examples/myapp/config/database.yml:
--------------------------------------------------------------------------------
1 | default: &default
2 | adapter: mysql2
3 | url: <%= ENV.fetch("DATABASE_URL") %>
4 |
5 | development:
6 | <<: *default
7 | database: kube_queue_myapp_development
8 |
9 | test:
10 | <<: *default
11 | database: kube_queue_myapp_test
12 |
13 | production:
14 | <<: *default
15 | database: kube_queue_myapp_production
16 |
--------------------------------------------------------------------------------
/examples/myapp/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
7 | # Character.create(name: 'Luke', movie: movies.first)
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 0.4.1
4 |
5 | - Fix `Worker.find` dosen't work
6 | - Add `Worker.template` accessor
7 |
8 | ## 0.4.0
9 |
10 | - Support scheduled job with Kubernetes CronJob.
11 | - Support more settings
12 | - `env_from_secret`
13 | - `env_from_configmap`
14 | - `cpu_limit`, `cpu_request`, `memory_limit`, `memory_request`
15 |
16 | ## 0.3.0
17 |
18 | - Support ActiveJob
19 | - Change command line interfaces
20 |
--------------------------------------------------------------------------------
/examples/myapp/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Myapp
5 | <%= csrf_meta_tags %>
6 | <%= csp_meta_tag %>
7 |
8 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
9 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
10 |
11 |
12 |
13 | <%= yield %>
14 |
15 |
16 |
--------------------------------------------------------------------------------
/lib/kube_queue/executor.rb:
--------------------------------------------------------------------------------
1 | require 'kube_queue/manifest_builder'
2 |
3 | module KubeQueue
4 | class Executor
5 | def enqueue(job)
6 | resource = if job.scheduled_at
7 | KubeQueue.client.create_cron_job(job.manifest)
8 | else
9 | KubeQueue.client.create_job(job.manifest)
10 | end
11 |
12 | job.resource = resource
13 | resource
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/examples/myapp/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/cloudbuild.yaml:
--------------------------------------------------------------------------------
1 | images: ['gcr.io/$PROJECT_ID/kube-queue:latest', 'gcr.io/$PROJECT_ID/kube-queue-test-app:latest']
2 |
3 | steps:
4 | - name: 'gcr.io/cloud-builders/docker'
5 | args: ['build', '-f', '/workspace/Dockerfile', '/workspace', '-t', 'gcr.io/$PROJECT_ID/kube-queue:latest']
6 |
7 | - name: 'gcr.io/cloud-builders/docker'
8 | args: ['build', '-f', '/workspace/Dockerfile.myapp', '/workspace', '-t', 'gcr.io/$PROJECT_ID/kube-queue-test-app:latest']
9 | waitFor: ['-']
10 |
--------------------------------------------------------------------------------
/examples/k8s/service-account.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: kube-queue-test
5 | namespace: kube-system
6 | ---
7 | apiVersion: rbac.authorization.k8s.io/v1
8 | kind: ClusterRoleBinding
9 | metadata:
10 | name: kube-queue-test
11 | roleRef:
12 | apiGroup: rbac.authorization.k8s.io
13 | kind: ClusterRole
14 | name: cluster-admin
15 | subjects:
16 | - kind: ServiceAccount
17 | name: kube-queue-test
18 | namespace: kube-system
19 |
--------------------------------------------------------------------------------
/examples/myapp/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | OVJtl6K3mHGJqrIvfbz4GLTN66nr/FcFSiuAOhUfZ+JAwr/RJUTZIYdauM4lz40+j4FFZB+AaChp4cTDvTs/wqzfO2qTpTqvp/3IAV+a6545maHsDw9f/TKKgO0rmi6XsALuvrRkJErYJWQgZj76KfWDrkr/YFUHfmvJDW4W453uiMn0q/b7hpn/WcKfn4S6BUepx3W0Ja/e/CJ2hIenURBUdKA7FqU0SB/9VjIR4h16obKRek3uwYs4J06eitZhyn5pSNQQpR4+j+/UjX0eVCojF+kAdaISElYc65TfUrtLDCVy56qZ2Or/GomRqvg7Oe4UmxtwHPw98KWVXLM5Jb2yE5cghcUEPHlgYvV9GhMRcmU7ehUEeDDBK/9YT2CzQ5cu999rICJk+3EvvahQ67dtbw1o0c85eeMa--mQVKxrjgiu/YYhDs--DOUx27FrQilSjqQ1Y3meLg==
--------------------------------------------------------------------------------
/spec/kube_queue_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | RSpec.describe KubeQueue do
4 | describe '.fetch_worker' do
5 | subject { KubeQueue.fetch_worker('TestWorker') }
6 |
7 | let!(:worker) do
8 | Class.new do
9 | def self.name
10 | 'TestWorker'
11 | end
12 |
13 | include KubeQueue::Worker
14 | end
15 | end
16 |
17 | after { KubeQueue.worker_registry.clear }
18 |
19 | it { is_expected.to eq worker }
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/examples/myapp/README.md:
--------------------------------------------------------------------------------
1 | # README
2 |
3 | This README would normally document whatever steps are necessary to get the
4 | application up and running.
5 |
6 | Things you may want to cover:
7 |
8 | * Ruby version
9 |
10 | * System dependencies
11 |
12 | * Configuration
13 |
14 | * Database creation
15 |
16 | * Database initialization
17 |
18 | * How to run the test suite
19 |
20 | * Services (job queues, cache servers, search engines, etc.)
21 |
22 | * Deployment instructions
23 |
24 | * ...
25 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | guard :rspec, cmd: "bundle exec rspec" do
2 | require "guard/rspec/dsl"
3 | dsl = Guard::RSpec::Dsl.new(self)
4 |
5 | rspec = dsl.rspec
6 | watch(rspec.spec_helper) { rspec.spec_dir }
7 | watch(rspec.spec_support) { rspec.spec_dir }
8 | watch(rspec.spec_files)
9 |
10 | ruby = dsl.ruby
11 | dsl.watch_spec_files_for(ruby.lib_files)
12 | end
13 |
14 | guard :rubocop do
15 | watch(%r{.+\.rb$})
16 | watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
17 | end
18 |
--------------------------------------------------------------------------------
/examples/myapp/app/jobs/import_task_job.rb:
--------------------------------------------------------------------------------
1 | class ImportTaskJob < ApplicationJob
2 | include KubeQueue::Worker
3 |
4 | worker_name 'import-task'
5 | image "gcr.io/#{ENV['PROJECT_ID']}/kube-queue-test-app"
6 | container_name 'kube-queue-test-app'
7 |
8 | env_from_secret 'myapp'
9 | env_from_config_map 'myapp'
10 |
11 | def perform(csv)
12 | Task.transaction do
13 | csv.each do |row|
14 | Task.create!(name: row[0], state: row[1])
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/examples/myapp/app/jobs/archive_done_tasks_job.rb:
--------------------------------------------------------------------------------
1 | class ArchiveDoneTasksJob < ApplicationJob
2 | include KubeQueue::Worker
3 |
4 | worker_name 'archive-done-task'
5 | image "gcr.io/#{ENV['PROJECT_ID']}/kube-queue-test-app"
6 | container_name 'kube-queue-test-app'
7 |
8 | env_from_secret 'myapp'
9 | env_from_config_map 'myapp'
10 |
11 | def perform
12 | Task.transaction do
13 | count = Task.done.delete_all
14 | logger.info "#{count} tasks were deleted."
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $LOAD_PATH << File.expand_path(__dir__)
2 |
3 | require "bundler/setup"
4 | require "kube_queue"
5 |
6 | require 'erbh'
7 |
8 | require_relative './workers'
9 |
10 | RSpec.configure do |config|
11 | # Enable flags like --only-failures and --next-failure
12 | config.example_status_persistence_file_path = ".rspec_status"
13 |
14 | # Disable RSpec exposing methods globally on `Module` and `main`
15 | config.disable_monkey_patching!
16 |
17 | config.expect_with :rspec do |c|
18 | c.syntax = :expect
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/examples/myapp/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/examples/myapp/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads Spring without using Bundler, in order to be fast.
4 | # It gets overwritten when you run the `spring binstub` command.
5 |
6 | unless defined?(Spring)
7 | require 'rubygems'
8 | require 'bundler'
9 |
10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
11 | spring = lockfile.specs.detect { |spec| spec.name == 'spring' }
12 | if spring
13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
14 | gem 'spring', spring.version
15 | require 'spring/binstub'
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby
2 |
3 | WORKDIR /app
4 |
5 | RUN apt-get update -y && apt-get install git --no-install-recommends && rm -r /var/cache/apt /var/lib/apt/lists
6 |
7 | RUN gem install bundler:2.0.2
8 | COPY examples/docker/Gemfile Gemfile
9 | COPY Gemfile kube_queue.gemspec .git /vendor/kube_queue/
10 | COPY exe/kube_queue /vendor/kube_queue/exe/kube_queue
11 | COPY lib/kube_queue/version.rb /vendor/kube_queue/lib/kube_queue/version.rb
12 | RUN bundle install -j4
13 |
14 | COPY examples/docker/test_worker.rb .
15 | COPY . /vendor/kube_queue
16 |
17 | CMD ["bundle", "exec", "kube_queue", "TestWorker", "-r", "./test_worker.rb"]
18 |
--------------------------------------------------------------------------------
/examples/myapp/app/controllers/import_tasks_controller.rb:
--------------------------------------------------------------------------------
1 | require 'csv'
2 |
3 | class ImportTasksController < ApplicationController
4 | rescue_from(StandardError) do |e|
5 | logger.error e
6 |
7 | redirect_to({ controller: :tasks, action: :index }, alert: 'Task import was failure')
8 | end
9 |
10 | def create
11 | file = params[:upload_file]
12 |
13 | csv = CSV.parse(file.read)
14 |
15 | ImportTaskJob.perform_later(csv)
16 |
17 | respond_to do |format|
18 | format.html { redirect_to({ controller: :tasks, action: :index }, notice: 'Task import was successfully started.') }
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "bundler/setup"
4 | require "kube_queue"
5 | require_relative "../examples/docker/test_worker"
6 |
7 | # You can add fixtures and/or initialization code here to make experimenting
8 | # with your gem easier. You can also use a different console, if you like.
9 |
10 | # (If you use this, don't forget to add pry to your Gemfile!)
11 | # require "pry"
12 | # Pry.start
13 |
14 | require "irb"
15 |
16 | KubeQueue.kubernetes_configure do |client|
17 | client.url = ENV['K8S_URL']
18 | client.ssl_ca_file = ENV['K8S_CA_CERT_FILE']
19 | client.auth_token = File.read(ENV['K8S_BEARER_TOKEN_FILE'])
20 | end
21 |
22 | IRB.start(__FILE__)
23 |
--------------------------------------------------------------------------------
/examples/myapp/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 | # Add Yarn node_modules folder to the asset load path.
9 | Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 |
11 | # Precompile additional assets.
12 | # application.js, application.css, and all non-JS/CSS in the app/assets
13 | # folder are already added.
14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
15 |
--------------------------------------------------------------------------------
/examples/docker/test_worker.rb:
--------------------------------------------------------------------------------
1 | require 'kube_queue'
2 |
3 | class TestWorker
4 | include KubeQueue::Worker
5 |
6 | worker_name 'kube-queue-test'
7 | image "gcr.io/#{ENV['PROJECT_ID']}/kube-queue"
8 | container_name 'kube-queue-test'
9 | command 'bundle', 'exec', 'kube_queue', 'runner', 'TestWorker', '-r', './test_worker.rb'
10 |
11 | def perform(payload)
12 | puts payload['message']
13 | end
14 | end
15 |
16 | # Run official example.
17 | # see: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/
18 | class TestWorker2
19 | include KubeQueue::Worker
20 |
21 | worker_name 'pi'
22 | image 'perl'
23 | container_name 'pi'
24 | command "perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"
25 | end
26 |
--------------------------------------------------------------------------------
/examples/myapp/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/tasks/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_with(model: task, local: true) do |form| %>
2 | <% if task.errors.any? %>
3 |
4 |
<%= pluralize(task.errors.count, "error") %> prohibited this task from being saved:
5 |
6 |
7 | <% task.errors.full_messages.each do |message| %>
8 | - <%= message %>
9 | <% end %>
10 |
11 |
12 | <% end %>
13 |
14 |
15 | <%= form.label :state %>
16 | <%= form.select :state, Task.states.keys.to_a, {} %>
17 |
18 |
19 |
20 | <%= form.label :name %>
21 | <%= form.text_field :name %>
22 |
23 |
24 |
25 | <%= form.submit %>
26 |
27 | <% end %>
28 |
--------------------------------------------------------------------------------
/examples/myapp/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
6 | * vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/examples/myapp/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative 'boot'
2 |
3 | require 'rails/all'
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 |
9 | module Myapp
10 | class Application < Rails::Application
11 | # Initialize configuration defaults for originally generated Rails version.
12 | config.load_defaults 5.2
13 | config.active_job.queue_adapter = :kube_queue
14 |
15 | # Settings in config/environments/* take precedence over those specified here.
16 | # Application configuration can go into files in config/initializers
17 | # -- all .rb files in that directory are automatically loaded after loading
18 | # the framework and any gems in your application.
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/Dockerfile.myapp:
--------------------------------------------------------------------------------
1 | FROM ruby
2 |
3 | RUN apt-get update -y && apt-get install git --no-install-recommends && rm -r /var/cache/apt /var/lib/apt/lists
4 |
5 | RUN gem install bundler:2.0.2
6 |
7 | ENV KUBE_QUEUE_PATH /vendor/kube_queue
8 | ENV WORKDIR /app
9 |
10 | WORKDIR $WORKDIR
11 |
12 | COPY .git $KUBE_QUEUE_PATH/.git
13 | COPY Gemfile kube_queue.gemspec $KUBE_QUEUE_PATH/
14 | COPY exe/kube_queue $KUBE_QUEUE_PATH/exe/kube_queue
15 | COPY lib/kube_queue/version.rb $KUBE_QUEUE_PATH/lib/kube_queue/version.rb
16 | COPY examples/myapp/Gemfile examples/myapp/Gemfile.lock $WORKDIR/
17 | RUN bundle install -j4
18 |
19 | COPY lib $KUBE_QUEUE_PATH/lib
20 | COPY template $KUBE_QUEUE_PATH/template
21 | COPY examples/myapp/ $WORKDIR
22 |
23 | RUN bundle exec rails assets:precompile
24 |
25 | CMD ["bundle", "exec", "rails", "console"]
26 |
--------------------------------------------------------------------------------
/examples/myapp/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
5 | // vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require rails-ujs
14 | //= require activestorage
15 | //= require turbolinks
16 | //= require_tree .
17 |
--------------------------------------------------------------------------------
/examples/myapp/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore the default SQLite database.
11 | /db/*.sqlite3
12 | /db/*.sqlite3-journal
13 |
14 | # Ignore all logfiles and tempfiles.
15 | /log/*
16 | /tmp/*
17 | !/log/.keep
18 | !/tmp/.keep
19 |
20 | # Ignore uploaded files in development
21 | /storage/*
22 | !/storage/.keep
23 |
24 | /node_modules
25 | /yarn-error.log
26 |
27 | /public/assets
28 | .byebug_history
29 |
30 | # Ignore master key for decrypting credentials and more.
31 | /config/master.key
32 |
--------------------------------------------------------------------------------
/examples/myapp/app/views/tasks/index.html.erb:
--------------------------------------------------------------------------------
1 | <%= notice %>
2 | <% if alert %>
3 | <%= alert %>
4 | <% end %>
5 |
6 | Tasks
7 |
8 |
9 |
10 |
11 | | State |
12 | Name |
13 | |
14 |
15 |
16 |
17 |
18 | <% @tasks.each do |task| %>
19 |
20 | | <%= task.state %> |
21 | <%= task.name %> |
22 | <%= link_to 'Show', task %> |
23 | <%= link_to 'Edit', edit_task_path(task) %> |
24 | <%= link_to 'Destroy', task, method: :delete, data: { confirm: 'Are you sure?' } %> |
25 |
26 | <% end %>
27 |
28 |
29 |
30 |
31 |
32 | <%= paginate @tasks %>
33 |
34 | <%= render 'import_form' %>
35 |
36 | <%= link_to 'New Task', new_task_path %>
37 |
--------------------------------------------------------------------------------
/examples/myapp/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | gem 'kube_queue', path: ENV['KUBE_QUEUE_PATH'] || '../../'
5 |
6 | gem 'rails', '~> 5.2.3'
7 | gem 'mysql2'
8 | gem 'puma', '~> 4.3'
9 | gem 'sass-rails', '~> 5.0'
10 | gem 'uglifier', '>= 1.3.0'
11 | gem 'mini_racer', platforms: :ruby
12 |
13 | gem 'coffee-rails', '~> 4.2'
14 | gem 'turbolinks', '~> 5'
15 | gem 'jbuilder', '~> 2.5'
16 |
17 | gem 'bootsnap', '>= 1.1.0', require: false
18 |
19 | gem 'pry-rails'
20 |
21 | gem 'kaminari'
22 |
23 | group :development, :test do
24 | gem 'byebug'
25 | gem 'pry-byebug'
26 | end
27 |
28 | group :development do
29 | gem 'web-console', '>= 3.3.0'
30 | gem 'listen', '>= 3.0.5', '< 3.2'
31 | gem 'spring'
32 | gem 'spring-watcher-listen', '~> 2.0.0'
33 | end
34 |
35 | group :test do
36 | gem 'capybara', '>= 2.15'
37 | gem 'selenium-webdriver'
38 | gem 'chromedriver-helper'
39 | end
40 |
--------------------------------------------------------------------------------
/examples/myapp/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a way to update your development environment automatically.
14 | # Add necessary update steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | puts "\n== Updating database =="
24 | system! 'bin/rails db:migrate'
25 |
26 | puts "\n== Removing old logs and tempfiles =="
27 | system! 'bin/rails log:clear tmp:clear'
28 |
29 | puts "\n== Restarting application server =="
30 | system! 'bin/rails restart'
31 | end
32 |
--------------------------------------------------------------------------------
/lib/active_job/adapters/kube_queue_adapter.rb:
--------------------------------------------------------------------------------
1 | require 'kube_queue'
2 |
3 | module ActiveJob
4 | module QueueAdapters
5 | # == KubeQueue adapter for ActiveJob ==
6 | #
7 | # To use KubeQueue set the queue_adapter config to +:kube_queue+.
8 | # Rails.application.config.active_job.queue_adapter = :kube_queue
9 | class KubeQueueAdapter
10 | class << self
11 | # Interface for ActiveJob 4.2
12 | def enqueue(job)
13 | KubeQueue.executor.enqueue(job)
14 | end
15 |
16 | def enqueue_at(job, timestamp)
17 | job.scheduled_at = timestamp
18 | KubeQueue.executor.enqueue(job)
19 | end
20 | end
21 |
22 | # Interface for ActiveJob 5.0
23 | def enqueue(job)
24 | KubeQueueAdapter.enqueue(job)
25 | end
26 |
27 | def enqueue_at(job, timestamp)
28 | job.scheduled_at = timestamp
29 | KubeQueueAdapter.enqueue(job)
30 | end
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/examples/myapp/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at http://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/spec/workers.rb:
--------------------------------------------------------------------------------
1 | class PrintMessageJob
2 | include KubeQueue::Worker
3 |
4 | worker_name 'print-message-job'
5 | image 'ruby'
6 | container_name 'ruby'
7 |
8 | def perform(payload)
9 | payload[:message]
10 | end
11 | end
12 |
13 | class MockClient
14 | def create_job(manifest)
15 | job = K8s::Resource.new(manifest)
16 | job.metadata.namespace ||= 'default'
17 | job
18 | end
19 |
20 | # overrider on test
21 | def get_job(_namespace, _name)
22 | raise NotImplementedError
23 | end
24 |
25 | # overrider on test
26 | def list_job(_job_class, _namespace = nil)
27 | []
28 | end
29 |
30 | def create_cron_job(manifest)
31 | cron_job = K8s::Resource.new(manifest)
32 | cron_job.metadata.namespace ||= 'default'
33 | cron_job
34 | end
35 | end
36 |
37 | class MockExecutor < KubeQueue::Executor
38 | def enqueue(job)
39 | resource = super
40 |
41 | job.perform_now
42 |
43 | resource
44 | end
45 | end
46 |
47 | KubeQueue.client = MockClient.new
48 | KubeQueue.executor = MockExecutor.new
49 |
--------------------------------------------------------------------------------
/spec/template/test.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | annotations:
5 | kube-queue-job-class: "<%= spec.job_class.name %>"
6 | kube-queue-job-id: "<%= job.job_id %>"
7 | kube-queue-job-payload: '<%= payload %>'
8 | name: "<%= spec.job_name(job.job_id) %>"
9 | namespace: <%= spec.namespace %>
10 | labels:
11 | kube-queue-job: "true"
12 | kube-queue-worker-name: "<%= spec.worker_name %>"
13 | kube-queue-job-class: "<%= spec.job_class.name %>"
14 | kube-queue-job-id: "<%= job.job_id %>"
15 | spec:
16 | template:
17 | metadata:
18 | annotations:
19 | kube-queue-job-class: "<%= spec.job_class.name %>"
20 | kube-queue-job-id: "<%= job.job_id %>"
21 | kube-queue-job-payload: '<%= payload %>'
22 | labels:
23 | kube-queue-job: "true"
24 | kube-queue-worker-name: "<%= spec.worker_name %>"
25 | kube-queue-job-class: "<%= spec.job_class.name %>"
26 | kube-queue-job-id: "<%= job.job_id %>"
27 | spec:
28 | containers:
29 | - name: "test-worker"
30 |
--------------------------------------------------------------------------------
/lib/kube_queue/manifest_builder.rb:
--------------------------------------------------------------------------------
1 | require 'erb'
2 | require 'json'
3 |
4 | module KubeQueue
5 | class ManifestBuilder
6 | attr_reader :job
7 |
8 | def initialize(job)
9 | @job = job
10 | end
11 |
12 | def spec
13 | job.job_spec
14 | end
15 |
16 | def payload
17 | JSON.generate(job.serialized_payload, quirks_mode: true)
18 | end
19 |
20 | def build_job
21 | YAML.safe_load(ERB.new(job.read_template, nil, "-").result(binding))
22 | end
23 |
24 | def build_cron_job(cron)
25 | template = YAML.safe_load(ERB.new(job.read_template, nil, "-").result(binding))
26 |
27 | {
28 | apiVersion: "batch/v1beta1",
29 | kind: "CronJob",
30 | metadata: template["metadata"],
31 | spec: {
32 | startingDeadlineSeconds: job.job_spec.starting_deadline_seconds,
33 | concurrentPolicy: job.job_spec.concurrent_policy,
34 | schedule: cron,
35 | jobTemplate: {
36 | spec: template["spec"]
37 | }
38 | }
39 | }
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/examples/myapp/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a starting point to setup your application.
14 | # Add necessary setup steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! 'bin/rails db:setup'
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! 'bin/rails log:clear tmp:clear'
33 |
34 | puts "\n== Restarting application server =="
35 | system! 'bin/rails restart'
36 | end
37 |
--------------------------------------------------------------------------------
/examples/myapp/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 2019_08_13_062954) do
14 |
15 | create_table "tasks", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
16 | t.integer "state", default: 0, null: false
17 | t.string "name", null: false
18 | t.datetime "created_at", null: false
19 | t.datetime "updated_at", null: false
20 | end
21 |
22 | end
23 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 yuemori
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/myapp/test/system/tasks_test.rb:
--------------------------------------------------------------------------------
1 | require "application_system_test_case"
2 |
3 | class TasksTest < ApplicationSystemTestCase
4 | setup do
5 | @task = tasks(:one)
6 | end
7 |
8 | test "visiting the index" do
9 | visit tasks_url
10 | assert_selector "h1", text: "Tasks"
11 | end
12 |
13 | test "creating a Task" do
14 | visit tasks_url
15 | click_on "New Task"
16 |
17 | fill_in "Name", with: @task.name
18 | fill_in "State", with: @task.state
19 | click_on "Create Task"
20 |
21 | assert_text "Task was successfully created"
22 | click_on "Back"
23 | end
24 |
25 | test "updating a Task" do
26 | visit tasks_url
27 | click_on "Edit", match: :first
28 |
29 | fill_in "Name", with: @task.name
30 | fill_in "State", with: @task.state
31 | click_on "Update Task"
32 |
33 | assert_text "Task was successfully updated"
34 | click_on "Back"
35 | end
36 |
37 | test "destroying a Task" do
38 | visit tasks_url
39 | page.accept_confirm do
40 | click_on "Destroy", match: :first
41 | end
42 |
43 | assert_text "Task was successfully destroyed"
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/kube_queue/client.rb:
--------------------------------------------------------------------------------
1 | require 'k8s-client'
2 | require 'pathname'
3 |
4 | module KubeQueue
5 | class Client
6 | def create_job(manifest)
7 | job = K8s::Resource.new(manifest)
8 | job.metadata.namespace ||= 'default'
9 | client.api('batch/v1').resource('jobs').create_resource(job)
10 | end
11 |
12 | def get_job(namespace, name)
13 | client.api('batch/v1').resource('jobs', namespace: namespace).get(name)
14 | end
15 |
16 | def list_job(job_class, namespace = nil)
17 | selector = { 'kube-queue-job': 'true', 'kube-queue-job-class': job_class }
18 | client.api('batch/v1').resource('jobs', namespace: namespace).list(labelSelector: selector)
19 | end
20 |
21 | def create_cron_job(manifest)
22 | cron_job = K8s::Resource.new(manifest)
23 | cron_job.metadata.namespace ||= 'default'
24 | client.api('batch/v1beta1').resource('cronjobs').create_resource(cron_job)
25 | end
26 |
27 | attr_accessor :url, :ssl_ca_file, :auth_token
28 |
29 | private
30 |
31 | def client
32 | @client ||= K8s.client(url, ssl_ca_file: ssl_ca_file, auth_token: auth_token)
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/examples/myapp/k8s/database.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | name: database
5 | labels:
6 | app: database
7 | spec:
8 | replicas: 1
9 | selector:
10 | matchLabels:
11 | app: database
12 | template:
13 | metadata:
14 | labels:
15 | app: database
16 | spec:
17 | containers:
18 | - image: mysql:5.7
19 | name: database
20 | imagePullPolicy: IfNotPresent
21 | env:
22 | - name: MYSQL_ALLOW_EMPTY_PASSWORD
23 | value: "true"
24 | ports:
25 | - containerPort: 3306
26 | livenessProbe:
27 | exec:
28 | command:
29 | - mysqladmin
30 | - ping
31 | readinessProbe:
32 | exec:
33 | command:
34 | - mysqladmin
35 | - ping
36 | restartPolicy: Always
37 | volumes:
38 | ---
39 | apiVersion: v1
40 | kind: Service
41 | metadata:
42 | labels:
43 | app: database
44 | name: database
45 | spec:
46 | type: ClusterIP
47 | ports:
48 | - name: mysql
49 | port: 3306
50 | protocol: TCP
51 | targetPort: 3306
52 | selector:
53 | app: database
54 |
--------------------------------------------------------------------------------
/examples/myapp/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.config.content_security_policy do |policy|
8 | # policy.default_src :self, :https
9 | # policy.font_src :self, :https, :data
10 | # policy.img_src :self, :https, :data
11 | # policy.object_src :none
12 | # policy.script_src :self, :https
13 | # policy.style_src :self, :https
14 |
15 | # # Specify URI for violation reports
16 | # # policy.report_uri "/csp-violation-report-endpoint"
17 | # end
18 |
19 | # If you are using UJS then enable automatic nonce generation
20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
21 |
22 | # Report CSP violations to a specified URI
23 | # For further information see the following documentation:
24 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
25 | # Rails.application.config.content_security_policy_report_only = true
26 |
--------------------------------------------------------------------------------
/examples/myapp/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket
23 |
24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/examples/myapp/test/controllers/tasks_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class TasksControllerTest < ActionDispatch::IntegrationTest
4 | setup do
5 | @task = tasks(:one)
6 | end
7 |
8 | test "should get index" do
9 | get tasks_url
10 | assert_response :success
11 | end
12 |
13 | test "should get new" do
14 | get new_task_url
15 | assert_response :success
16 | end
17 |
18 | test "should create task" do
19 | assert_difference('Task.count') do
20 | post tasks_url, params: { task: { name: @task.name, state: @task.state } }
21 | end
22 |
23 | assert_redirected_to task_url(Task.last)
24 | end
25 |
26 | test "should show task" do
27 | get task_url(@task)
28 | assert_response :success
29 | end
30 |
31 | test "should get edit" do
32 | get edit_task_url(@task)
33 | assert_response :success
34 | end
35 |
36 | test "should update task" do
37 | patch task_url(@task), params: { task: { name: @task.name, state: @task.state } }
38 | assert_redirected_to task_url(@task)
39 | end
40 |
41 | test "should destroy task" do
42 | assert_difference('Task.count', -1) do
43 | delete task_url(@task)
44 | end
45 |
46 | assert_redirected_to tasks_url
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | require:
2 | - rubocop-performance
3 |
4 | AllCops:
5 | TargetRubyVersion: 2.5
6 | Exclude:
7 | - Gemfile
8 | - Rakefile
9 | - examples/**/*
10 | - kube_queue.gemspec
11 | - Guardfile
12 | - vendor/**/*
13 | DisplayCopNames: true
14 |
15 | Gemspec/OrderedDependencies:
16 | Enabled: false
17 |
18 | Style/NumericLiterals:
19 | Enabled: false
20 |
21 | Style/StderrPuts:
22 | Enabled: false
23 |
24 | Naming/MethodParameterName:
25 | Enabled: false
26 |
27 | Style/StringLiterals:
28 | Enabled: false
29 |
30 | Style/ExpandPathArguments:
31 | Enabled: false
32 |
33 | Style/PercentLiteralDelimiters:
34 | Enabled: false
35 |
36 | Style/FrozenStringLiteralComment:
37 | Enabled: false
38 |
39 | Style/Documentation:
40 | Enabled: false
41 |
42 | Layout/LineLength:
43 | Max: 128
44 |
45 | Style/EmptyCaseCondition:
46 | Enabled: false
47 |
48 | Layout/ArgumentAlignment:
49 | Enabled: false
50 |
51 | Metrics/MethodLength:
52 | Max: 15
53 |
54 | Metrics/AbcSize:
55 | Max: 25
56 |
57 | Style/ParallelAssignment:
58 | Enabled: false
59 |
60 | Style/Alias:
61 | EnforcedStyle: prefer_alias_method
62 |
63 | Metrics/BlockLength:
64 | Exclude:
65 | - 'spec/**/*.rb'
66 |
67 | Style/MixinUsage:
68 | Exclude:
69 | - 'spec/**/*.rb'
70 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | sudo: false
3 | cache: bundler
4 | rvm:
5 | - 2.4.10
6 | - 2.6.6
7 | - 2.7.1
8 | - ruby-head
9 | branches:
10 | only:
11 | - master
12 | before_install:
13 | - gem update --system
14 | - gem update --remote bundler
15 | install: bundle install --job=3 --retry=3
16 | script:
17 | - bundle exec rspec
18 | gemfile:
19 | - gemfiles/rails_5.1.gemfile
20 | - gemfiles/rails_5.2.gemfile
21 | - gemfiles/rails_6.0.gemfile
22 | matrix:
23 | allow_failures:
24 | - rvm: ruby-head
25 | fast_finish: true
26 | notifications:
27 | slack:
28 | secure: P996C+m/+CeH9RacgDyVWYY7jdOKR3/FA194GwXE+l5Yc51AtH0Us8EWhaizNUPmxIO9DI8wFrX+/RYTl0mV2dOAToj655yTNIQAPh+FFHbC3dpjwHejXP90cWstwdR/PBPsy71sMk476RQPl4jWzR+OOoCQdyEWPUlYnVhu+DR2Tt54ixcoZqb7Li/s5gQCY7RDXBlO/lNA4nvslHTUTtdlHYM9MnUFtvYa/aCQ16bvHk/HdzUKUiZ1mZ1q+DDc104KFwcTxRMyyP3zfL0A5fNFsUoajVkifPBOi+3rrj2dlrwQJp7bv/WdBrv8R7WXbZLOx3EOZLnFzqmy63Hb2n6rbpAtYuILdAzmL4cW9/sGZCbnCUsBmwh/xk3/wTEUdHHqgLIkjPsvD0Z/AJmSf/ub3rsBG9mIvpDSyB+ok0ymsqda2W3Fe5+8zJ7AikO8W9OwhBoGLOaTBJKxl7ujy+z6BZjt4FaOUtl1StxjVk7j6Pzc0Io8EOronoe+PMJF2AthdtSZglf0CvHaKT43nh+mKmXv8hk2o/APpuSGdH5/0HBxyiziSZZJisEBHAkk8Zyv2SDG7KWyjz3GVpRBbFeRG7zZEbZ8QJSfN2d6eqa7lYEnyyTXZk2irkCLuMS7WJgGybgGPna49sj4/9YoithMPomPIz9Zt2/3SsKX/nc=
29 | on_success: change
30 | on_failure: always
31 |
--------------------------------------------------------------------------------
/examples/myapp/app/assets/stylesheets/scaffolds.scss:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #fff;
3 | color: #333;
4 | margin: 33px;
5 | font-family: verdana, arial, helvetica, sans-serif;
6 | font-size: 13px;
7 | line-height: 18px;
8 | }
9 |
10 | p, ol, ul, td {
11 | font-family: verdana, arial, helvetica, sans-serif;
12 | font-size: 13px;
13 | line-height: 18px;
14 | }
15 |
16 | pre {
17 | background-color: #eee;
18 | padding: 10px;
19 | font-size: 11px;
20 | }
21 |
22 | a {
23 | color: #000;
24 |
25 | &:visited {
26 | color: #666;
27 | }
28 |
29 | &:hover {
30 | color: #fff;
31 | background-color: #000;
32 | }
33 | }
34 |
35 | th {
36 | padding-bottom: 5px;
37 | }
38 |
39 | td {
40 | padding: 0 5px 7px;
41 | }
42 |
43 | div {
44 | &.field, &.actions {
45 | margin-bottom: 10px;
46 | }
47 | }
48 |
49 | #notice {
50 | color: green;
51 | }
52 |
53 | .field_with_errors {
54 | padding: 2px;
55 | background-color: red;
56 | display: table;
57 | }
58 |
59 | #error_explanation {
60 | width: 450px;
61 | border: 2px solid red;
62 | padding: 7px 7px 0;
63 | margin-bottom: 20px;
64 | background-color: #f0f0f0;
65 |
66 | h2 {
67 | text-align: left;
68 | font-weight: bold;
69 | padding: 5px 5px 5px 15px;
70 | font-size: 12px;
71 | margin: -7px -7px 0;
72 | background-color: #c00;
73 | color: #fff;
74 | }
75 |
76 | ul li {
77 | font-size: 12px;
78 | list-style: square;
79 | }
80 | }
81 |
82 | label {
83 | display: block;
84 | }
85 |
--------------------------------------------------------------------------------
/lib/kube_queue.rb:
--------------------------------------------------------------------------------
1 | require "kube_queue/version"
2 | require "kube_queue/executor"
3 | require "kube_queue/configuration"
4 | require "kube_queue/worker"
5 | require "kube_queue/client"
6 | require "active_job/adapters/kube_queue_adapter" if defined?(Rails)
7 |
8 | module KubeQueue
9 | class JobNotFound < StandardError; end
10 |
11 | class << self
12 | attr_writer :executor, :client
13 |
14 | def executor
15 | @executor ||= default_executor
16 | end
17 |
18 | def kubernetes_configure
19 | yield client
20 | end
21 |
22 | def client
23 | @client ||= default_client
24 | end
25 |
26 | def default_executor
27 | Executor.new
28 | end
29 |
30 | def default_client
31 | Client.new
32 | end
33 |
34 | def configure(&block)
35 | configuration.configure(&block)
36 | end
37 |
38 | def configuration
39 | @configuration ||= Configuration.new
40 | end
41 |
42 | attr_writer :default_env
43 |
44 | def default_env
45 | return @default_env if @default_env
46 |
47 | return {} unless defined?(Rails)
48 |
49 | {
50 | RAILS_LOG_TO_STDOUT: ENV['RAILS_LOG_TO_STDOUT'],
51 | RAILS_ENV: ENV['RAILS_ENV']
52 | }
53 | end
54 |
55 | def fetch_worker(name)
56 | worker_registry.fetch(name)
57 | end
58 |
59 | def register_worker(name, klass)
60 | worker_registry[name] = klass
61 | end
62 |
63 | def worker_registry
64 | @worker_registry ||= {}
65 | end
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/examples/myapp/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
11 | #
12 | port ENV.fetch("PORT") { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch("RAILS_ENV") { "development" }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25 |
26 | # Use the `preload_app!` method when specifying a `workers` number.
27 | # This directive tells Puma to first boot the application and load code
28 | # before forking the application. This takes advantage of Copy On Write
29 | # process behavior so workers use less memory.
30 | #
31 | # preload_app!
32 |
33 | # Allow puma to be restarted by `rails restart` command.
34 | plugin :tmp_restart
35 |
--------------------------------------------------------------------------------
/examples/myapp/app/controllers/tasks_controller.rb:
--------------------------------------------------------------------------------
1 | class TasksController < ApplicationController
2 | before_action :set_task, only: [:show, :edit, :update, :destroy]
3 |
4 | def index
5 | @tasks = Task.page(params[:page])
6 | end
7 |
8 | def show
9 | end
10 |
11 | def new
12 | @task = Task.new
13 | end
14 |
15 | def edit
16 | end
17 |
18 | def create
19 | @task = Task.new(task_params)
20 |
21 | respond_to do |format|
22 | if @task.save
23 | format.html { redirect_to @task, notice: 'Task was successfully created.' }
24 | format.json { render :show, status: :created, location: @task }
25 | else
26 | format.html { render :new }
27 | format.json { render json: @task.errors, status: :unprocessable_entity }
28 | end
29 | end
30 | end
31 |
32 | def update
33 | respond_to do |format|
34 | if @task.update(task_params)
35 | format.html { redirect_to @task, notice: 'Task was successfully updated.' }
36 | format.json { render :show, status: :ok, location: @task }
37 | else
38 | format.html { render :edit }
39 | format.json { render json: @task.errors, status: :unprocessable_entity }
40 | end
41 | end
42 | end
43 |
44 | def destroy
45 | @task.destroy
46 | respond_to do |format|
47 | format.html { redirect_to tasks_url, notice: 'Task was successfully destroyed.' }
48 | format.json { head :no_content }
49 | end
50 | end
51 |
52 | private
53 |
54 | def set_task
55 | @task = Task.find(params[:id])
56 | end
57 |
58 | # Never trust parameters from the scary internet, only allow the white list through.
59 | def task_params
60 | params.require(:task).permit(:state, :name)
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/spec/active_job/adapters/kube_queue_adapter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | require 'active_job'
4 | require "active_job/adapters/kube_queue_adapter"
5 |
6 | RSpec.describe ActiveJob::QueueAdapters::KubeQueueAdapter do
7 | let(:job_class) do
8 | Class.new(ActiveJob::Base) do
9 | self.queue_adapter = :kube_queue
10 |
11 | include KubeQueue::Worker
12 |
13 | worker_name 'print-message-job'
14 | image 'ruby'
15 | container_name 'ruby'
16 |
17 | def perform(payload)
18 | payload[:message]
19 | end
20 | end
21 | end
22 |
23 | before do
24 | ActiveJob::Base.logger = Logger.new('/dev/null')
25 | end
26 |
27 | describe '.perform_later' do
28 | subject { job_class.perform_later(arg) }
29 |
30 | let(:arg) { { message: 'hello' } }
31 |
32 | context 'when doesnt set delay' do
33 | before do
34 | expect_any_instance_of(job_class).to receive(:perform)
35 | expect(KubeQueue.client).to receive(:create_job).and_call_original
36 | end
37 |
38 | it { expect { subject }.not_to raise_error }
39 | end
40 |
41 | context 'when set delay' do
42 | subject { job_class.set(wait: 600).perform_later }
43 |
44 | before do
45 | expect_any_instance_of(job_class).to receive(:perform)
46 | expect(KubeQueue.client).to receive(:create_cron_job).and_call_original
47 | end
48 |
49 | it { expect { subject }.not_to raise_error }
50 | end
51 | end
52 |
53 | describe '.perform_now' do
54 | subject { job_class.perform_now(arg) }
55 |
56 | before { expect_any_instance_of(job_class).to receive(:perform).with(arg) }
57 |
58 | let(:arg) { { message: 'hello' } }
59 |
60 | it { expect { subject }.not_to raise_error }
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/examples/myapp/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/kube_queue.gemspec:
--------------------------------------------------------------------------------
1 | lib = File.expand_path("lib", __dir__)
2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3 | require "kube_queue/version"
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = "kube_queue"
7 | spec.version = KubeQueue::VERSION
8 | spec.authors = ["yuemori"]
9 | spec.email = ["yuemori@aiming-inc.com"]
10 |
11 | spec.summary = "A background job processing with Kubernetes job for Ruby"
12 | spec.description = "A background job processing with Kubernetes job for Ruby"
13 | spec.homepage = "https://github.com/yuemori/kube_queue"
14 | spec.license = "MIT"
15 |
16 | spec.metadata["homepage_uri"] = spec.homepage
17 | spec.metadata["source_code_uri"] = "https://github.com/yuemori/kube_queue"
18 | spec.metadata["changelog_uri"] = "https://github.com/yuemori/kube_queue/CHANGELOG.md"
19 |
20 | spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|examples|cloudbuild.yaml)/}) }
22 | end
23 | spec.bindir = "exe"
24 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25 | spec.require_paths = ["lib"]
26 |
27 | spec.add_dependency "k8s-client"
28 | spec.add_dependency "thor"
29 | spec.add_development_dependency "bundler", "~> 2.0"
30 | spec.add_development_dependency "rake", "~> 10.0"
31 | spec.add_development_dependency "rspec", "~> 3.0"
32 | spec.add_development_dependency "erbh"
33 | spec.add_development_dependency "rubocop"
34 | spec.add_development_dependency "rubocop-performance"
35 | spec.add_development_dependency "rubocop-rspec"
36 | spec.add_development_dependency "pry-byebug"
37 | spec.add_development_dependency "guard"
38 | spec.add_development_dependency "guard-rspec"
39 | spec.add_development_dependency "guard-rubocop"
40 | spec.add_development_dependency "activejob"
41 | spec.add_development_dependency "appraisal"
42 | end
43 |
--------------------------------------------------------------------------------
/examples/myapp/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/examples/myapp/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/lib/kube_queue/worker/dsl.rb:
--------------------------------------------------------------------------------
1 | module KubeQueue
2 | module Worker
3 | module DSL
4 | def job_spec
5 | @job_spec ||= JobSpecification.new(self)
6 | end
7 |
8 | def worker_name(name)
9 | job_spec.worker_name = name
10 | end
11 |
12 | def container_name(container_name)
13 | job_spec.container_name = container_name
14 | end
15 |
16 | def image(image)
17 | job_spec.image = image
18 | end
19 |
20 | def namespace(namespace)
21 | job_spec.namespace = namespace
22 | end
23 |
24 | def command(*command)
25 | job_spec.command = command
26 | end
27 |
28 | def restart_policy(policy)
29 | job_spec.restart_policy = policy
30 | end
31 |
32 | def active_deadline_seconds(seconds)
33 | job_spec.active_deadline_seconds = seconds.to_s
34 | end
35 |
36 | def backoff_limit(limit)
37 | job_spec.backoff_limit = limit
38 | end
39 |
40 | def env(env)
41 | job_spec.env = env
42 | end
43 |
44 | def labels(labels)
45 | job_spec.labels = labels
46 | end
47 |
48 | def env_from_config_map(*config_map_names)
49 | job_spec.env_from_config_map = config_map_names
50 | end
51 |
52 | def env_from_secret(*secret_names)
53 | job_spec.env_from_config_map = secret_names
54 | end
55 |
56 | def cpu_limit(limit)
57 | job_spec.cpu_limit = limit
58 | end
59 |
60 | def memory_limit(limit)
61 | job_spec.memory_limit = limit
62 | end
63 |
64 | def cpu_request(request)
65 | job_spec.cpu_request = request
66 | end
67 |
68 | def memory_request(request)
69 | job_spec.memory_request = request
70 | end
71 |
72 | def starting_deadline_seconds(seconds)
73 | job_spec.starting_deadline_seconds = seconds
74 | end
75 |
76 | def concurrent_policy(policy)
77 | job_spec.concurrent_policy = policy
78 | end
79 | end
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/examples/myapp/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 |
31 | # Store uploaded files on the local file system in a temporary directory
32 | config.active_storage.service = :test
33 |
34 | config.action_mailer.perform_caching = false
35 |
36 | # Tell Action Mailer not to deliver emails to the real world.
37 | # The :test delivery method accumulates sent emails in the
38 | # ActionMailer::Base.deliveries array.
39 | config.action_mailer.delivery_method = :test
40 |
41 | # Print deprecation notices to the stderr.
42 | config.active_support.deprecation = :stderr
43 |
44 | # Raises error for missing translations
45 | # config.action_view.raise_on_missing_translations = true
46 | end
47 |
--------------------------------------------------------------------------------
/exe/kube_queue:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'rubygems'
4 | require 'thor'
5 | require 'kube_queue'
6 |
7 | module KubeQueue
8 | class CLI < Thor
9 | default_task :version
10 |
11 | desc 'runner JOB_NAME [PAYLOAD]', 'run worker'
12 | method_option :require, aliases: '-r', type: :string, desc: 'Location of Rails application with workers or file to require'
13 | method_option :rails, aliases: '-R', type: :boolean, desc: 'Location of Rails application with workers or file to require'
14 | def runner(job_name, payload = nil)
15 | load_files!
16 |
17 | # Infer application work on rails if require option does not specified.
18 | load_rails! if !options[:require] || options[:rails]
19 |
20 | payload ||= ENV['KUBE_QUEUE_MESSAGE_PAYLOAD']
21 | payload = JSON.parse(payload) if payload
22 | # Compatibility for ActiveJob serialized payload
23 | payload = [payload] unless payload.is_a?(Array)
24 | payload = ActiveJob::Arguments.deserialize(payload) if defined?(ActiveJob::Arguments)
25 |
26 | job = KubeQueue.fetch_worker(job_name).new(*payload)
27 |
28 | job.perform_now
29 | end
30 |
31 | desc 'version', 'Prints version'
32 | def version
33 | say "KubeQueue version #{KubeQueue::VERSION}"
34 | end
35 |
36 | private
37 |
38 | def load_files!
39 | return unless options[:require]
40 |
41 | raise "#{options[:require]} dosent exist." unless File.exist?(options[:require])
42 |
43 | files = File.directory?(options[:require]) ? Dir.glob(File.join(options[:require], '**/*.rb')) : [options[:require]]
44 |
45 | files.each do |file|
46 | require file
47 | end
48 | end
49 |
50 | def load_rails!
51 | require "rails"
52 |
53 | raise "KubeQueue does not supports this version of Rails" if ::Rails::VERSION::MAJOR < 5
54 |
55 | require 'rails'
56 | require 'kube_queue/railties'
57 | require File.expand_path('config/environment.rb')
58 | Rails.application.eager_load!
59 | end
60 | end
61 | end
62 |
63 | KubeQueue::CLI.start
64 |
--------------------------------------------------------------------------------
/lib/kube_queue/job_specification.rb:
--------------------------------------------------------------------------------
1 | require 'erb'
2 | require 'yaml'
3 |
4 | module KubeQueue
5 | class JobSpecification
6 | class MissingParameterError < StandardError; end
7 |
8 | attr_reader :job_class
9 |
10 | attr_accessor :payload, :name, :active_deadline_seconds, :backoff_limit, :starting_deadline_seconds,
11 | :cpu_limit, :memory_limit, :cpu_request, :memory_request
12 |
13 | attr_writer :image, :namespace, :worker_name, :command,
14 | :container_name, :restart_policy, :job_labels, :pod_labels,
15 | :env_from_config_map, :env_from_secret, :concurrent_policy, :env
16 |
17 | def initialize(job_class)
18 | @job_class = job_class
19 | end
20 |
21 | def job_name(job_id)
22 | "#{worker_name}-#{job_id}"
23 | end
24 |
25 | def image
26 | @image || raise_not_found_required_parameter('image')
27 | end
28 |
29 | def namespace
30 | @namespace || 'default'
31 | end
32 |
33 | def worker_name
34 | @worker_name || raise_not_found_required_parameter('worker_name')
35 | end
36 |
37 | def container_name
38 | @container_name || worker_name
39 | end
40 |
41 | def command
42 | @command || ['bundle', 'exec', 'kube_queue', 'runner', job_class.name]
43 | end
44 |
45 | def restart_policy
46 | @restart_policy || 'Never'
47 | end
48 |
49 | def job_labels
50 | @job_labels || {}
51 | end
52 |
53 | def pod_labels
54 | @pod_labels || {}
55 | end
56 |
57 | def env
58 | KubeQueue.default_env.merge(@env || {})
59 | end
60 |
61 | def env_from_config_map
62 | @env_from_config_map || []
63 | end
64 |
65 | def env_from_secret
66 | @env_from_config_map || []
67 | end
68 |
69 | def env_from_exists?
70 | !env_from_config_map.empty? && !env_from_secret.empty?
71 | end
72 |
73 | def concurrent_policy
74 | @concurrent_policy || 'Allow'
75 | end
76 |
77 | def resources_exists?
78 | @cpu_limit || @memory_limit || @cpu_request || @memory_request
79 | end
80 |
81 | def raise_not_found_required_parameter(field)
82 | raise MissingParameterError, "#{field} is required"
83 | end
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/examples/myapp/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | # Run rails dev:cache to toggle caching.
17 | if Rails.root.join('tmp', 'caching-dev.txt').exist?
18 | config.action_controller.perform_caching = true
19 |
20 | config.cache_store = :memory_store
21 | config.public_file_server.headers = {
22 | 'Cache-Control' => "public, max-age=#{2.days.to_i}"
23 | }
24 | else
25 | config.action_controller.perform_caching = false
26 |
27 | config.cache_store = :null_store
28 | end
29 |
30 | # Store uploaded files on the local file system (see config/storage.yml for options)
31 | config.active_storage.service = :local
32 |
33 | # Don't care if the mailer can't send.
34 | config.action_mailer.raise_delivery_errors = false
35 |
36 | config.action_mailer.perform_caching = false
37 |
38 | # Print deprecation notices to the Rails logger.
39 | config.active_support.deprecation = :log
40 |
41 | # Raise an error on page load if there are pending migrations.
42 | config.active_record.migration_error = :page_load
43 |
44 | # Highlight code that triggered database queries in logs.
45 | config.active_record.verbose_query_logs = true
46 |
47 | # Debug mode disables concatenation and preprocessing of assets.
48 | # This option may cause significant delays in view rendering with a large
49 | # number of complex assets.
50 | config.assets.debug = true
51 |
52 | # Suppress logger output for asset requests.
53 | config.assets.quiet = true
54 |
55 | # Raises error for missing translations
56 | # config.action_view.raise_on_missing_translations = true
57 |
58 | # Use an evented file watcher to asynchronously detect changes in source code,
59 | # routes, locales, etc. This feature depends on the listen gem.
60 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
61 | end
62 |
--------------------------------------------------------------------------------
/examples/myapp/k8s/app.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: kube-queue-test-app
5 | namespace: default
6 | ---
7 | apiVersion: rbac.authorization.k8s.io/v1
8 | kind: ClusterRoleBinding
9 | metadata:
10 | name: kube-queue-test-app
11 | roleRef:
12 | apiGroup: rbac.authorization.k8s.io
13 | kind: ClusterRole
14 | name: cluster-admin
15 | subjects:
16 | - kind: ServiceAccount
17 | name: kube-queue-test-app
18 | namespace: default
19 | ---
20 | apiVersion: v1
21 | kind: Secret
22 | metadata:
23 | name: myapp
24 | namespace: default
25 | data:
26 | DATABASE_URL: bXlzcWwyOi8vcm9vdEBkYXRhYmFzZTozMzA2
27 | SECRET_KEY_BASE: ZTkxMzUzYzcwZTYyMzUwZTJmMTE1YWVkNjlmNjhhNWQ0MjhiMjkyMzM3ZTdlOWIxYmIwMzRhY2Y5ZDZlYTA4YjQ5OTE1NmU1MGQxNWRlNjZkYTQ2YTAwYzFhZmQwNzNhM2ZiNzRmNzIwMTFiZThiYjliYThlNjliZWRiMTc3NjE=
28 | type: Opaque
29 | ---
30 | apiVersion: v1
31 | kind: ConfigMap
32 | metadata:
33 | name: myapp
34 | namespace: default
35 | data:
36 | K8S_URL: https://kubernetes.default:443
37 | K8S_BEARER_TOKEN_FILE: /var/run/secrets/kubernetes.io/serviceaccount/token
38 | K8S_CA_CERT_FILE: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
39 | PROJECT_ID: savvy-temple-184102
40 | LANG: C.UTF-8
41 | LOG_LEVEL: DEBUG
42 | RAILS_ENV: production
43 | RAILS_LOG_TO_STDOUT: "true"
44 | RAILS_SERVE_STATIC_FILES: "1"
45 | ---
46 | apiVersion: v1
47 | kind: Service
48 | metadata:
49 | name: myapp
50 | namespace: default
51 | spec:
52 | type: ClusterIP
53 | ports:
54 | - name: myapp
55 | port: 3000
56 | targetPort: 3000
57 | protocol: TCP
58 | selector:
59 | app: myapp
60 | ---
61 | apiVersion: apps/v1
62 | kind: Deployment
63 | metadata:
64 | name: myapp
65 | namespace: default
66 | spec:
67 | replicas: 1
68 | selector:
69 | matchLabels:
70 | app: myapp
71 | template:
72 | metadata:
73 | name: myapp
74 | labels:
75 | app: myapp
76 | spec:
77 | serviceAccountName: kube-queue-test-app
78 | initContainers:
79 | - name: myapp-migration
80 | image: 'gcr.io/savvy-temple-184102/kube-queue-test-app'
81 | command: ["bundle", "exec", "rails", "db:create", "db:migrate"]
82 | envFrom:
83 | - configMapRef:
84 | name: myapp
85 | - secretRef:
86 | name: myapp
87 | containers:
88 | - name: myapp
89 | image: 'gcr.io/savvy-temple-184102/kube-queue-test-app'
90 | command: ["bundle", "exec", "rails", "server"]
91 | envFrom:
92 | - configMapRef:
93 | name: myapp
94 | - secretRef:
95 | name: myapp
96 | ports:
97 | - containerPort: 3000
98 |
--------------------------------------------------------------------------------
/template/job.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | annotations:
5 | kube-queue-job-class: "<%= spec.job_class.name %>"
6 | kube-queue-job-id: "<%= job.job_id %>"
7 | kube-queue-job-payload: '<%= payload %>'
8 | name: "<%= spec.job_name(job.job_id) %>"
9 | namespace: <%= spec.namespace %>
10 | labels:
11 | kube-queue-job: "true"
12 | kube-queue-worker-name: "<%= spec.worker_name %>"
13 | kube-queue-job-class: "<%= spec.job_class.name %>"
14 | kube-queue-job-id: "<%= job.job_id %>"
15 | spec:
16 | template:
17 | metadata:
18 | annotations:
19 | kube-queue-job-class: "<%= spec.job_class.name %>"
20 | kube-queue-job-id: "<%= job.job_id %>"
21 | kube-queue-job-payload: '<%= payload %>'
22 | labels:
23 | kube-queue-job: "true"
24 | kube-queue-worker-name: "<%= spec.worker_name %>"
25 | kube-queue-job-class: "<%= spec.job_class.name %>"
26 | kube-queue-job-id: "<%= job.job_id %>"
27 | <%- spec.job_labels.each do |key, value| %>
28 | <%= key %>: "<%= value %>"
29 | <%- end %>
30 | spec:
31 | containers:
32 | - name: "<%= spec.container_name %>"
33 | image: "<%= spec.image %>"
34 | command: <%= spec.command %>
35 | env:
36 | - name: "KUBE_QUEUE_MESSAGE_PAYLOAD"
37 | value: '<%= payload %>'
38 | <%- spec.env.each do |key, value| %>
39 | - name: "<%= key %>"
40 | value: "<%= value %>"
41 | <%- end %>
42 | <%- if spec.env_from_exists? %>
43 | envFrom:
44 | <%- spec.env_from_config_map.each do |name| %>
45 | - configMapRef:
46 | name: "<%= name %>"
47 | <%- end %>
48 | <%- spec.env_from_secret.each do |name| %>
49 | - secretRef:
50 | name: "<%= name %>"
51 | <%- end %>
52 | <%- end %>
53 | <%- if spec.resources_exists? %>
54 | resources:
55 | <%- if spec.cpu_limit || spec.memory_limit %>
56 | limits:
57 | <%- if spec.cpu_limit %>
58 | cpu: <%= spec.cpu_limit %>
59 | <%- end %>
60 | <%- if spec.memory_limit %>
61 | memory: <%= spec.memory_limit %>
62 | <%- end %>
63 | <%- end %>
64 | <%- if spec.cpu_request || spec.memory_request %>
65 | requests:
66 | <%- if spec.cpu_request %>
67 | cpu: <%= spec.cpu_request %>
68 | <%- end %>
69 | <%- if spec.memory_request %>
70 | memory: <%= spec.memory_request %>
71 | <%- end %>
72 | <%- end %>
73 | <%- else %>
74 | resources: {}
75 | <%- end %>
76 | restartPolicy: "<%= spec.restart_policy %>"
77 | <%- if spec.backoff_limit %>
78 | backoffLimit: <%= spec.backoff_limit %>
79 | <%- end %>
80 | <%- if spec.active_deadline_seconds %>
81 | activeDeadlineSeconds: <%= spec.active_deadline_seconds %>
82 | <%- end %>
83 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at yuemori@aiming-inc.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/examples/myapp/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
19 | # config.require_master_key = true
20 |
21 | # Disable serving static files from the `/public` folder by default since
22 | # Apache or NGINX already handles this.
23 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
24 |
25 | # Compress JavaScripts and CSS.
26 | config.assets.js_compressor = :uglifier
27 | # config.assets.css_compressor = :sass
28 |
29 | # Do not fallback to assets pipeline if a precompiled asset is missed.
30 | config.assets.compile = false
31 |
32 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
33 |
34 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
35 | # config.action_controller.asset_host = 'http://assets.example.com'
36 |
37 | # Specifies the header that your server uses for sending files.
38 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
40 |
41 | # Store uploaded files on the local file system (see config/storage.yml for options)
42 | config.active_storage.service = :local
43 |
44 | # Mount Action Cable outside main process or domain
45 | # config.action_cable.mount_path = nil
46 | # config.action_cable.url = 'wss://example.com/cable'
47 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
48 |
49 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
50 | # config.force_ssl = true
51 |
52 | # Use the lowest log level to ensure availability of diagnostic information
53 | # when problems arise.
54 | config.log_level = :debug
55 |
56 | # Prepend all log lines with the following tags.
57 | config.log_tags = [ :request_id ]
58 |
59 | # Use a different cache store in production.
60 | # config.cache_store = :mem_cache_store
61 |
62 | # Use a real queuing backend for Active Job (and separate queues per environment)
63 | # config.active_job.queue_adapter = :resque
64 | # config.active_job.queue_name_prefix = "myapp_#{Rails.env}"
65 |
66 | config.action_mailer.perform_caching = false
67 |
68 | # Ignore bad email addresses and do not raise email delivery errors.
69 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
70 | # config.action_mailer.raise_delivery_errors = false
71 |
72 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
73 | # the I18n.default_locale when a translation cannot be found).
74 | config.i18n.fallbacks = true
75 |
76 | # Send deprecation notices to registered listeners.
77 | config.active_support.deprecation = :notify
78 |
79 | # Use default logging formatter so that PID and timestamp are not suppressed.
80 | config.log_formatter = ::Logger::Formatter.new
81 |
82 | # Use a different logger for distributed setups.
83 | # require 'syslog/logger'
84 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
85 |
86 | if ENV["RAILS_LOG_TO_STDOUT"].present?
87 | logger = ActiveSupport::Logger.new(STDOUT)
88 | logger.formatter = config.log_formatter
89 | config.logger = ActiveSupport::TaggedLogging.new(logger)
90 | end
91 |
92 | # Do not dump schema after migrations.
93 | config.active_record.dump_schema_after_migration = false
94 | end
95 |
--------------------------------------------------------------------------------
/lib/kube_queue/worker.rb:
--------------------------------------------------------------------------------
1 | require 'kube_queue/worker/dsl'
2 | require 'kube_queue/job_specification'
3 |
4 | module KubeQueue
5 | module Worker
6 | def self.included(base)
7 | base.extend(ClassMethods)
8 |
9 | KubeQueue.register_worker(base.name, base)
10 | end
11 |
12 | module ClassMethods
13 | include DSL
14 |
15 | attr_writer :template
16 |
17 | def template
18 | @template ||= File.expand_path('../../../template/job.yaml', __FILE__)
19 | end
20 |
21 | def active_job?
22 | defined?(ActiveJob) && ancestors.include?(ActiveJob::Base)
23 | end
24 |
25 | def list
26 | namespace = job_spec.namespace
27 |
28 | KubeQueue.client.list_job(job_spec.job_class, namespace).map do |res|
29 | worker = KubeQueue.fetch_worker(res.metadata.annotations['kube-queue-job-class'])
30 | job = worker.new(*payload)
31 | job.resource = res
32 | job
33 | end
34 | end
35 |
36 | def find(job_id)
37 | namespace = job_spec.namespace
38 |
39 | name = job_spec.job_name(job_id)
40 |
41 | res = KubeQueue.client.get_job(namespace, name)
42 | worker = KubeQueue.fetch_worker(res.metadata.annotations['kube-queue-job-class'])
43 |
44 | payload = deserialize_annotation_payload(res.metadata.annotations['kube-queue-job-payload'])
45 |
46 | job = worker.new(*payload)
47 | job.resource = res
48 | job
49 | end
50 |
51 | def enqueue(*args)
52 | job = new(*args)
53 | KubeQueue.executor.enqueue(job)
54 | job
55 | end
56 | alias_method :perform_async, :enqueue
57 |
58 | def enqueue_at(*args)
59 | args = args.dup
60 | timestamp = args.pop
61 | job = new(*args)
62 | job.scheduled_at = timestamp
63 | KubeQueue.executor.enqueue(job)
64 | job
65 | end
66 |
67 | def read_template
68 | File.read(template)
69 | end
70 |
71 | def manifest
72 | new.manifest
73 | end
74 |
75 | private
76 |
77 | def deserialize_annotation_payload(payload)
78 | return payload if payload.empty?
79 |
80 | payload = JSON.parse(payload)
81 |
82 | # Compatibility for ActiveJob serialized payload
83 | payload = [payload] unless payload.is_a?(Array)
84 |
85 | payload = ActiveJob::Arguments.deserialize(payload) if defined?(ActiveJob::Arguments)
86 |
87 | payload
88 | end
89 | end
90 |
91 | def read_template
92 | self.class.read_template
93 | end
94 |
95 | def job_spec
96 | self.class.job_spec
97 | end
98 |
99 | attr_accessor :job_id, :scheduled_at
100 | attr_reader :arguments, :resource
101 |
102 | alias_method :payload, :arguments
103 |
104 | def initialize(*arguments)
105 | # Compatibility for ActiveJob interface
106 | if method(__method__).super_method.arity.zero?
107 | super()
108 | else
109 | super
110 | end
111 |
112 | @arguments = arguments
113 | @job_id = SecureRandom.uuid
114 | @loaded = false
115 | end
116 |
117 | def perform_now
118 | # Compatibility for ActiveJob interface
119 | return super if defined?(super)
120 |
121 | perform(*arguments)
122 | end
123 |
124 | def perform(*)
125 | raise NotImplementedError
126 | end
127 |
128 | def status
129 | return @resource.status if loaded?
130 |
131 | load_target
132 |
133 | @resource.status
134 | end
135 |
136 | def loaded?
137 | @loaded
138 | end
139 |
140 | def reload!
141 | @loaded = false
142 | @resource = nil
143 |
144 | load_target
145 | end
146 |
147 | def manifest
148 | if scheduled_at
149 | # Kubernetes CronJob does not support timezone
150 | cron = Time.at(scheduled_at).utc.strftime("%M %H %d %m %w")
151 | ManifestBuilder.new(self).build_cron_job(cron)
152 | else
153 | ManifestBuilder.new(self).build_job
154 | end
155 | end
156 |
157 | def serialized_payload
158 | if self.class.active_job?
159 | ActiveJob::Arguments.serialize(arguments)
160 | else
161 | arguments
162 | end
163 | end
164 |
165 | def resource=(resource)
166 | @resource = resource
167 | self.job_id = resource.metadata.annotations['kube-queue-job-id']
168 | end
169 |
170 | private
171 |
172 | def load_target
173 | self.resource = KubeQueue.client.get_job(job_spec.namespace, job_spec.job_name(job_id))
174 | @loaded = true
175 | end
176 | end
177 | end
178 |
--------------------------------------------------------------------------------
/spec/kube_queue/manifest_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'erbh'
3 |
4 | RSpec.describe 'KubeQueue generated manifest' do
5 | include ERBh
6 |
7 | class TestWorker
8 | include KubeQueue::Worker
9 |
10 | worker_name 'test-worker'
11 | image 'test-image'
12 | end
13 |
14 | let(:job) { TestWorker.new }
15 |
16 | let(:manifest) { job.manifest }
17 |
18 | describe 'default' do
19 | it 'matches default manifest' do
20 | expect(manifest).to match YAML.safe_load(erbh(<<~ERB, job_id: job.job_id))
21 | apiVersion: batch/v1
22 | kind: Job
23 | metadata:
24 | annotations:
25 | kube-queue-job-class: "TestWorker"
26 | kube-queue-job-id: "<%= @job_id %>"
27 | kube-queue-job-payload: '[]'
28 | name: "test-worker-<%= @job_id %>"
29 | namespace: default
30 | labels:
31 | kube-queue-job: "true"
32 | kube-queue-worker-name: "test-worker"
33 | kube-queue-job-class: "TestWorker"
34 | kube-queue-job-id: "<%= @job_id %>"
35 | spec:
36 | template:
37 | metadata:
38 | annotations:
39 | kube-queue-job-class: "TestWorker"
40 | kube-queue-job-id: "<%= @job_id %>"
41 | kube-queue-job-payload: '[]'
42 | labels:
43 | kube-queue-job: "true"
44 | kube-queue-worker-name: "test-worker"
45 | kube-queue-job-class: "TestWorker"
46 | kube-queue-job-id: "<%= @job_id %>"
47 | spec:
48 | containers:
49 | - name: "test-worker"
50 | image: "test-image"
51 | command: ["bundle", "exec", "kube_queue", "runner", "TestWorker"]
52 | env:
53 | - name: "KUBE_QUEUE_MESSAGE_PAYLOAD"
54 | value: '[]'
55 | resources: {}
56 | restartPolicy: "Never"
57 | ERB
58 | end
59 | end
60 |
61 | describe 'payload' do
62 | let(:job) { TestWorker.new(test: true) }
63 |
64 | subject(:env) { manifest['spec']['template']['spec']['containers'][0]['env'] }
65 |
66 | it 'sets to KUBE_QUEUE_MESSAGE_PAYLOAD' do
67 | expect(env).to include(
68 | "name" => "KUBE_QUEUE_MESSAGE_PAYLOAD",
69 | "value" => "[#{{ test: true }.to_json}]"
70 | )
71 | end
72 | end
73 |
74 | describe 'env' do
75 | before do
76 | TestWorker.env(
77 | TEST: true,
78 | RAILS_ENV: "production"
79 | )
80 | end
81 |
82 | after do
83 | TestWorker.env(nil)
84 | end
85 |
86 | subject(:env) { manifest['spec']['template']['spec']['containers'][0]['env'] }
87 |
88 | it 'includes env' do
89 | expect(env).to match_array(
90 | [
91 | {
92 | "name" => "KUBE_QUEUE_MESSAGE_PAYLOAD",
93 | "value" => "[]"
94 | },
95 | {
96 | "name" => "TEST",
97 | "value" => "true"
98 | },
99 | {
100 | "name" => "RAILS_ENV",
101 | "value" => "production"
102 | }
103 | ]
104 | )
105 | end
106 | end
107 |
108 | describe 'template' do
109 | before do
110 | TestWorker.template = File.expand_path('../../template/test.yaml', __FILE__)
111 | end
112 |
113 | it 'matches test manifest' do
114 | expect(manifest).to match YAML.safe_load(erbh(<<~ERB, job_id: job.job_id))
115 | apiVersion: batch/v1
116 | kind: Job
117 | metadata:
118 | annotations:
119 | kube-queue-job-class: "TestWorker"
120 | kube-queue-job-id: "<%= @job_id %>"
121 | kube-queue-job-payload: '[]'
122 | name: "test-worker-<%= @job_id %>"
123 | namespace: default
124 | labels:
125 | kube-queue-job: "true"
126 | kube-queue-worker-name: "test-worker"
127 | kube-queue-job-class: "TestWorker"
128 | kube-queue-job-id: "<%= @job_id %>"
129 | spec:
130 | template:
131 | metadata:
132 | annotations:
133 | kube-queue-job-class: "TestWorker"
134 | kube-queue-job-id: "<%= @job_id %>"
135 | kube-queue-job-payload: '[]'
136 | labels:
137 | kube-queue-job: "true"
138 | kube-queue-worker-name: "test-worker"
139 | kube-queue-job-class: "TestWorker"
140 | kube-queue-job-id: "<%= @job_id %>"
141 | spec:
142 | containers:
143 | - name: "test-worker"
144 | ERB
145 | end
146 |
147 | after do
148 | TestWorker.template = nil
149 | end
150 | end
151 | end
152 |
--------------------------------------------------------------------------------
/gemfiles/rails_5.1.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ..
3 | specs:
4 | kube_queue (0.4.0)
5 | k8s-client
6 | thor
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | activejob (5.1.7)
12 | activesupport (= 5.1.7)
13 | globalid (>= 0.3.6)
14 | activesupport (5.1.7)
15 | concurrent-ruby (~> 1.0, >= 1.0.2)
16 | i18n (>= 0.7, < 2)
17 | minitest (~> 5.1)
18 | tzinfo (~> 1.1)
19 | appraisal (2.2.0)
20 | bundler
21 | rake
22 | thor (>= 0.14.0)
23 | ast (2.4.0)
24 | byebug (11.0.1)
25 | coderay (1.1.2)
26 | concurrent-ruby (1.1.5)
27 | diff-lcs (1.3)
28 | dry-configurable (0.8.3)
29 | concurrent-ruby (~> 1.0)
30 | dry-core (~> 0.4, >= 0.4.7)
31 | dry-container (0.7.2)
32 | concurrent-ruby (~> 1.0)
33 | dry-configurable (~> 0.1, >= 0.1.3)
34 | dry-core (0.4.9)
35 | concurrent-ruby (~> 1.0)
36 | dry-equalizer (0.2.2)
37 | dry-inflector (0.1.2)
38 | dry-logic (0.6.1)
39 | concurrent-ruby (~> 1.0)
40 | dry-core (~> 0.2)
41 | dry-equalizer (~> 0.2)
42 | dry-struct (0.5.1)
43 | dry-core (~> 0.4, >= 0.4.3)
44 | dry-equalizer (~> 0.2)
45 | dry-types (~> 0.13)
46 | ice_nine (~> 0.11)
47 | dry-types (0.13.4)
48 | concurrent-ruby (~> 1.0)
49 | dry-container (~> 0.3)
50 | dry-core (~> 0.4, >= 0.4.4)
51 | dry-equalizer (~> 0.2)
52 | dry-inflector (~> 0.1, >= 0.1.2)
53 | dry-logic (~> 0.4, >= 0.4.2)
54 | erbh (0.1.3)
55 | excon (0.66.0)
56 | ffi (1.11.1)
57 | formatador (0.2.5)
58 | globalid (0.4.2)
59 | activesupport (>= 4.2.0)
60 | guard (2.15.0)
61 | formatador (>= 0.2.4)
62 | listen (>= 2.7, < 4.0)
63 | lumberjack (>= 1.0.12, < 2.0)
64 | nenv (~> 0.1)
65 | notiffany (~> 0.0)
66 | pry (>= 0.9.12)
67 | shellany (~> 0.0)
68 | thor (>= 0.18.1)
69 | guard-compat (1.2.1)
70 | guard-rspec (4.7.3)
71 | guard (~> 2.1)
72 | guard-compat (~> 1.1)
73 | rspec (>= 2.99.0, < 4.0)
74 | guard-rubocop (1.3.0)
75 | guard (~> 2.0)
76 | rubocop (~> 0.20)
77 | hashdiff (1.0.0)
78 | i18n (1.6.0)
79 | concurrent-ruby (~> 1.0)
80 | ice_nine (0.11.2)
81 | jaro_winkler (1.5.3)
82 | jsonpath (0.9.9)
83 | multi_json
84 | to_regexp (~> 0.2.1)
85 | k8s-client (0.10.3)
86 | dry-struct (~> 0.5.0)
87 | dry-types (~> 0.13.0)
88 | excon (~> 0.66)
89 | hashdiff (~> 1.0.0)
90 | jsonpath (~> 0.9.5)
91 | recursive-open-struct (~> 1.1.0)
92 | yajl-ruby (~> 1.4.0)
93 | yaml-safe_load_stream (~> 0.1)
94 | listen (3.1.5)
95 | rb-fsevent (~> 0.9, >= 0.9.4)
96 | rb-inotify (~> 0.9, >= 0.9.7)
97 | ruby_dep (~> 1.2)
98 | lumberjack (1.0.13)
99 | method_source (0.9.2)
100 | minitest (5.11.3)
101 | multi_json (1.13.1)
102 | nenv (0.3.0)
103 | notiffany (0.1.3)
104 | nenv (~> 0.1)
105 | shellany (~> 0.0)
106 | parallel (1.17.0)
107 | parser (2.6.3.0)
108 | ast (~> 2.4.0)
109 | pry (0.12.2)
110 | coderay (~> 1.1.0)
111 | method_source (~> 0.9.0)
112 | pry-byebug (3.7.0)
113 | byebug (~> 11.0)
114 | pry (~> 0.10)
115 | rainbow (3.0.0)
116 | rake (10.5.0)
117 | rb-fsevent (0.10.3)
118 | rb-inotify (0.10.0)
119 | ffi (~> 1.0)
120 | recursive-open-struct (1.1.0)
121 | rspec (3.8.0)
122 | rspec-core (~> 3.8.0)
123 | rspec-expectations (~> 3.8.0)
124 | rspec-mocks (~> 3.8.0)
125 | rspec-core (3.8.2)
126 | rspec-support (~> 3.8.0)
127 | rspec-expectations (3.8.4)
128 | diff-lcs (>= 1.2.0, < 2.0)
129 | rspec-support (~> 3.8.0)
130 | rspec-mocks (3.8.1)
131 | diff-lcs (>= 1.2.0, < 2.0)
132 | rspec-support (~> 3.8.0)
133 | rspec-support (3.8.2)
134 | rubocop (0.74.0)
135 | jaro_winkler (~> 1.5.1)
136 | parallel (~> 1.10)
137 | parser (>= 2.6)
138 | rainbow (>= 2.2.2, < 4.0)
139 | ruby-progressbar (~> 1.7)
140 | unicode-display_width (>= 1.4.0, < 1.7)
141 | rubocop-performance (1.4.1)
142 | rubocop (>= 0.71.0)
143 | rubocop-rspec (1.35.0)
144 | rubocop (>= 0.60.0)
145 | ruby-progressbar (1.10.1)
146 | ruby_dep (1.5.0)
147 | shellany (0.0.1)
148 | thor (0.20.3)
149 | thread_safe (0.3.6)
150 | to_regexp (0.2.1)
151 | tzinfo (1.2.5)
152 | thread_safe (~> 0.1)
153 | unicode-display_width (1.6.0)
154 | yajl-ruby (1.4.1)
155 | yaml-safe_load_stream (0.1.1)
156 |
157 | PLATFORMS
158 | ruby
159 |
160 | DEPENDENCIES
161 | activejob (= 5.1.7)
162 | appraisal
163 | bundler (~> 2.0)
164 | erbh
165 | guard
166 | guard-rspec
167 | guard-rubocop
168 | kube_queue!
169 | pry-byebug
170 | rake (~> 10.0)
171 | rspec (~> 3.0)
172 | rubocop
173 | rubocop-performance
174 | rubocop-rspec
175 |
176 | BUNDLED WITH
177 | 2.0.2
178 |
--------------------------------------------------------------------------------
/gemfiles/rails_5.2.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ..
3 | specs:
4 | kube_queue (0.4.0)
5 | k8s-client
6 | thor
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | activejob (5.2.3)
12 | activesupport (= 5.2.3)
13 | globalid (>= 0.3.6)
14 | activesupport (5.2.3)
15 | concurrent-ruby (~> 1.0, >= 1.0.2)
16 | i18n (>= 0.7, < 2)
17 | minitest (~> 5.1)
18 | tzinfo (~> 1.1)
19 | appraisal (2.2.0)
20 | bundler
21 | rake
22 | thor (>= 0.14.0)
23 | ast (2.4.0)
24 | byebug (11.0.1)
25 | coderay (1.1.2)
26 | concurrent-ruby (1.1.5)
27 | diff-lcs (1.3)
28 | dry-configurable (0.8.3)
29 | concurrent-ruby (~> 1.0)
30 | dry-core (~> 0.4, >= 0.4.7)
31 | dry-container (0.7.2)
32 | concurrent-ruby (~> 1.0)
33 | dry-configurable (~> 0.1, >= 0.1.3)
34 | dry-core (0.4.9)
35 | concurrent-ruby (~> 1.0)
36 | dry-equalizer (0.2.2)
37 | dry-inflector (0.1.2)
38 | dry-logic (0.6.1)
39 | concurrent-ruby (~> 1.0)
40 | dry-core (~> 0.2)
41 | dry-equalizer (~> 0.2)
42 | dry-struct (0.5.1)
43 | dry-core (~> 0.4, >= 0.4.3)
44 | dry-equalizer (~> 0.2)
45 | dry-types (~> 0.13)
46 | ice_nine (~> 0.11)
47 | dry-types (0.13.4)
48 | concurrent-ruby (~> 1.0)
49 | dry-container (~> 0.3)
50 | dry-core (~> 0.4, >= 0.4.4)
51 | dry-equalizer (~> 0.2)
52 | dry-inflector (~> 0.1, >= 0.1.2)
53 | dry-logic (~> 0.4, >= 0.4.2)
54 | erbh (0.1.3)
55 | excon (0.66.0)
56 | ffi (1.11.1)
57 | formatador (0.2.5)
58 | globalid (0.4.2)
59 | activesupport (>= 4.2.0)
60 | guard (2.15.0)
61 | formatador (>= 0.2.4)
62 | listen (>= 2.7, < 4.0)
63 | lumberjack (>= 1.0.12, < 2.0)
64 | nenv (~> 0.1)
65 | notiffany (~> 0.0)
66 | pry (>= 0.9.12)
67 | shellany (~> 0.0)
68 | thor (>= 0.18.1)
69 | guard-compat (1.2.1)
70 | guard-rspec (4.7.3)
71 | guard (~> 2.1)
72 | guard-compat (~> 1.1)
73 | rspec (>= 2.99.0, < 4.0)
74 | guard-rubocop (1.3.0)
75 | guard (~> 2.0)
76 | rubocop (~> 0.20)
77 | hashdiff (1.0.0)
78 | i18n (1.6.0)
79 | concurrent-ruby (~> 1.0)
80 | ice_nine (0.11.2)
81 | jaro_winkler (1.5.3)
82 | jsonpath (0.9.9)
83 | multi_json
84 | to_regexp (~> 0.2.1)
85 | k8s-client (0.10.3)
86 | dry-struct (~> 0.5.0)
87 | dry-types (~> 0.13.0)
88 | excon (~> 0.66)
89 | hashdiff (~> 1.0.0)
90 | jsonpath (~> 0.9.5)
91 | recursive-open-struct (~> 1.1.0)
92 | yajl-ruby (~> 1.4.0)
93 | yaml-safe_load_stream (~> 0.1)
94 | listen (3.1.5)
95 | rb-fsevent (~> 0.9, >= 0.9.4)
96 | rb-inotify (~> 0.9, >= 0.9.7)
97 | ruby_dep (~> 1.2)
98 | lumberjack (1.0.13)
99 | method_source (0.9.2)
100 | minitest (5.11.3)
101 | multi_json (1.13.1)
102 | nenv (0.3.0)
103 | notiffany (0.1.3)
104 | nenv (~> 0.1)
105 | shellany (~> 0.0)
106 | parallel (1.17.0)
107 | parser (2.6.3.0)
108 | ast (~> 2.4.0)
109 | pry (0.12.2)
110 | coderay (~> 1.1.0)
111 | method_source (~> 0.9.0)
112 | pry-byebug (3.7.0)
113 | byebug (~> 11.0)
114 | pry (~> 0.10)
115 | rainbow (3.0.0)
116 | rake (10.5.0)
117 | rb-fsevent (0.10.3)
118 | rb-inotify (0.10.0)
119 | ffi (~> 1.0)
120 | recursive-open-struct (1.1.0)
121 | rspec (3.8.0)
122 | rspec-core (~> 3.8.0)
123 | rspec-expectations (~> 3.8.0)
124 | rspec-mocks (~> 3.8.0)
125 | rspec-core (3.8.2)
126 | rspec-support (~> 3.8.0)
127 | rspec-expectations (3.8.4)
128 | diff-lcs (>= 1.2.0, < 2.0)
129 | rspec-support (~> 3.8.0)
130 | rspec-mocks (3.8.1)
131 | diff-lcs (>= 1.2.0, < 2.0)
132 | rspec-support (~> 3.8.0)
133 | rspec-support (3.8.2)
134 | rubocop (0.74.0)
135 | jaro_winkler (~> 1.5.1)
136 | parallel (~> 1.10)
137 | parser (>= 2.6)
138 | rainbow (>= 2.2.2, < 4.0)
139 | ruby-progressbar (~> 1.7)
140 | unicode-display_width (>= 1.4.0, < 1.7)
141 | rubocop-performance (1.4.1)
142 | rubocop (>= 0.71.0)
143 | rubocop-rspec (1.35.0)
144 | rubocop (>= 0.60.0)
145 | ruby-progressbar (1.10.1)
146 | ruby_dep (1.5.0)
147 | shellany (0.0.1)
148 | thor (0.20.3)
149 | thread_safe (0.3.6)
150 | to_regexp (0.2.1)
151 | tzinfo (1.2.5)
152 | thread_safe (~> 0.1)
153 | unicode-display_width (1.6.0)
154 | yajl-ruby (1.4.1)
155 | yaml-safe_load_stream (0.1.1)
156 |
157 | PLATFORMS
158 | ruby
159 |
160 | DEPENDENCIES
161 | activejob (= 5.2.3)
162 | appraisal
163 | bundler (~> 2.0)
164 | erbh
165 | guard
166 | guard-rspec
167 | guard-rubocop
168 | kube_queue!
169 | pry-byebug
170 | rake (~> 10.0)
171 | rspec (~> 3.0)
172 | rubocop
173 | rubocop-performance
174 | rubocop-rspec
175 |
176 | BUNDLED WITH
177 | 2.0.2
178 |
--------------------------------------------------------------------------------
/gemfiles/rails_6.0.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ..
3 | specs:
4 | kube_queue (0.4.0)
5 | k8s-client
6 | thor
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | activejob (6.0.0.beta2)
12 | activesupport (= 6.0.0.beta2)
13 | globalid (>= 0.3.6)
14 | activesupport (6.0.0.beta2)
15 | concurrent-ruby (~> 1.0, >= 1.0.2)
16 | i18n (>= 0.7, < 2)
17 | minitest (~> 5.1)
18 | tzinfo (~> 1.1)
19 | zeitwerk (~> 1.3, >= 1.3.1)
20 | appraisal (2.2.0)
21 | bundler
22 | rake
23 | thor (>= 0.14.0)
24 | ast (2.4.0)
25 | byebug (11.0.1)
26 | coderay (1.1.2)
27 | concurrent-ruby (1.1.5)
28 | diff-lcs (1.3)
29 | dry-configurable (0.8.3)
30 | concurrent-ruby (~> 1.0)
31 | dry-core (~> 0.4, >= 0.4.7)
32 | dry-container (0.7.2)
33 | concurrent-ruby (~> 1.0)
34 | dry-configurable (~> 0.1, >= 0.1.3)
35 | dry-core (0.4.9)
36 | concurrent-ruby (~> 1.0)
37 | dry-equalizer (0.2.2)
38 | dry-inflector (0.1.2)
39 | dry-logic (0.6.1)
40 | concurrent-ruby (~> 1.0)
41 | dry-core (~> 0.2)
42 | dry-equalizer (~> 0.2)
43 | dry-struct (0.5.1)
44 | dry-core (~> 0.4, >= 0.4.3)
45 | dry-equalizer (~> 0.2)
46 | dry-types (~> 0.13)
47 | ice_nine (~> 0.11)
48 | dry-types (0.13.4)
49 | concurrent-ruby (~> 1.0)
50 | dry-container (~> 0.3)
51 | dry-core (~> 0.4, >= 0.4.4)
52 | dry-equalizer (~> 0.2)
53 | dry-inflector (~> 0.1, >= 0.1.2)
54 | dry-logic (~> 0.4, >= 0.4.2)
55 | erbh (0.1.3)
56 | excon (0.66.0)
57 | ffi (1.11.1)
58 | formatador (0.2.5)
59 | globalid (0.4.2)
60 | activesupport (>= 4.2.0)
61 | guard (2.15.0)
62 | formatador (>= 0.2.4)
63 | listen (>= 2.7, < 4.0)
64 | lumberjack (>= 1.0.12, < 2.0)
65 | nenv (~> 0.1)
66 | notiffany (~> 0.0)
67 | pry (>= 0.9.12)
68 | shellany (~> 0.0)
69 | thor (>= 0.18.1)
70 | guard-compat (1.2.1)
71 | guard-rspec (4.7.3)
72 | guard (~> 2.1)
73 | guard-compat (~> 1.1)
74 | rspec (>= 2.99.0, < 4.0)
75 | guard-rubocop (1.3.0)
76 | guard (~> 2.0)
77 | rubocop (~> 0.20)
78 | hashdiff (1.0.0)
79 | i18n (1.6.0)
80 | concurrent-ruby (~> 1.0)
81 | ice_nine (0.11.2)
82 | jaro_winkler (1.5.3)
83 | jsonpath (0.9.9)
84 | multi_json
85 | to_regexp (~> 0.2.1)
86 | k8s-client (0.10.3)
87 | dry-struct (~> 0.5.0)
88 | dry-types (~> 0.13.0)
89 | excon (~> 0.66)
90 | hashdiff (~> 1.0.0)
91 | jsonpath (~> 0.9.5)
92 | recursive-open-struct (~> 1.1.0)
93 | yajl-ruby (~> 1.4.0)
94 | yaml-safe_load_stream (~> 0.1)
95 | listen (3.1.5)
96 | rb-fsevent (~> 0.9, >= 0.9.4)
97 | rb-inotify (~> 0.9, >= 0.9.7)
98 | ruby_dep (~> 1.2)
99 | lumberjack (1.0.13)
100 | method_source (0.9.2)
101 | minitest (5.11.3)
102 | multi_json (1.13.1)
103 | nenv (0.3.0)
104 | notiffany (0.1.3)
105 | nenv (~> 0.1)
106 | shellany (~> 0.0)
107 | parallel (1.17.0)
108 | parser (2.6.3.0)
109 | ast (~> 2.4.0)
110 | pry (0.12.2)
111 | coderay (~> 1.1.0)
112 | method_source (~> 0.9.0)
113 | pry-byebug (3.7.0)
114 | byebug (~> 11.0)
115 | pry (~> 0.10)
116 | rainbow (3.0.0)
117 | rake (10.5.0)
118 | rb-fsevent (0.10.3)
119 | rb-inotify (0.10.0)
120 | ffi (~> 1.0)
121 | recursive-open-struct (1.1.0)
122 | rspec (3.8.0)
123 | rspec-core (~> 3.8.0)
124 | rspec-expectations (~> 3.8.0)
125 | rspec-mocks (~> 3.8.0)
126 | rspec-core (3.8.2)
127 | rspec-support (~> 3.8.0)
128 | rspec-expectations (3.8.4)
129 | diff-lcs (>= 1.2.0, < 2.0)
130 | rspec-support (~> 3.8.0)
131 | rspec-mocks (3.8.1)
132 | diff-lcs (>= 1.2.0, < 2.0)
133 | rspec-support (~> 3.8.0)
134 | rspec-support (3.8.2)
135 | rubocop (0.74.0)
136 | jaro_winkler (~> 1.5.1)
137 | parallel (~> 1.10)
138 | parser (>= 2.6)
139 | rainbow (>= 2.2.2, < 4.0)
140 | ruby-progressbar (~> 1.7)
141 | unicode-display_width (>= 1.4.0, < 1.7)
142 | rubocop-performance (1.4.1)
143 | rubocop (>= 0.71.0)
144 | rubocop-rspec (1.35.0)
145 | rubocop (>= 0.60.0)
146 | ruby-progressbar (1.10.1)
147 | ruby_dep (1.5.0)
148 | shellany (0.0.1)
149 | thor (0.20.3)
150 | thread_safe (0.3.6)
151 | to_regexp (0.2.1)
152 | tzinfo (1.2.5)
153 | thread_safe (~> 0.1)
154 | unicode-display_width (1.6.0)
155 | yajl-ruby (1.4.1)
156 | yaml-safe_load_stream (0.1.1)
157 | zeitwerk (1.4.3)
158 |
159 | PLATFORMS
160 | ruby
161 |
162 | DEPENDENCIES
163 | activejob (= 6.0.0.beta2)
164 | appraisal
165 | bundler (~> 2.0)
166 | erbh
167 | guard
168 | guard-rspec
169 | guard-rubocop
170 | kube_queue!
171 | pry-byebug
172 | rake (~> 10.0)
173 | rspec (~> 3.0)
174 | rubocop
175 | rubocop-performance
176 | rubocop-rspec
177 |
178 | BUNDLED WITH
179 | 2.0.2
180 |
--------------------------------------------------------------------------------
/examples/myapp/tasks.csv:
--------------------------------------------------------------------------------
1 |
2 | task1,doing
3 | task2,doing
4 | task3,doing
5 | task4,doing
6 | task5,doing
7 | task6,doing
8 | task7,doing
9 | task8,doing
10 | task9,doing
11 | task10,doing
12 | task11,doing
13 | task12,doing
14 | task13,doing
15 | task14,doing
16 | task15,doing
17 | task16,doing
18 | task17,doing
19 | task18,doing
20 | task19,doing
21 | task20,doing
22 | task21,doing
23 | task22,doing
24 | task23,doing
25 | task24,doing
26 | task25,doing
27 | task26,doing
28 | task27,doing
29 | task28,doing
30 | task29,doing
31 | task30,doing
32 | task31,doing
33 | task32,doing
34 | task33,doing
35 | task34,doing
36 | task35,doing
37 | task36,doing
38 | task37,doing
39 | task38,doing
40 | task39,doing
41 | task40,doing
42 | task41,doing
43 | task42,doing
44 | task43,doing
45 | task44,doing
46 | task45,doing
47 | task46,doing
48 | task47,doing
49 | task48,doing
50 | task49,doing
51 | task50,doing
52 | task51,doing
53 | task52,doing
54 | task53,doing
55 | task54,doing
56 | task55,doing
57 | task56,doing
58 | task57,doing
59 | task58,doing
60 | task59,doing
61 | task60,doing
62 | task61,doing
63 | task62,doing
64 | task63,doing
65 | task64,doing
66 | task65,doing
67 | task66,doing
68 | task67,doing
69 | task68,doing
70 | task69,doing
71 | task70,doing
72 | task71,doing
73 | task72,doing
74 | task73,doing
75 | task74,doing
76 | task75,doing
77 | task76,doing
78 | task77,doing
79 | task78,doing
80 | task79,doing
81 | task80,doing
82 | task81,doing
83 | task82,doing
84 | task83,doing
85 | task84,doing
86 | task85,doing
87 | task86,doing
88 | task87,doing
89 | task88,doing
90 | task89,doing
91 | task90,doing
92 | task91,doing
93 | task92,doing
94 | task93,doing
95 | task94,doing
96 | task95,doing
97 | task96,doing
98 | task97,doing
99 | task98,doing
100 | task99,doing
101 | task100,doing
102 | task101,todo
103 | task102,todo
104 | task103,todo
105 | task104,todo
106 | task105,todo
107 | task106,todo
108 | task107,todo
109 | task108,todo
110 | task109,todo
111 | task110,todo
112 | task111,todo
113 | task112,todo
114 | task113,todo
115 | task114,todo
116 | task115,todo
117 | task116,todo
118 | task117,todo
119 | task118,todo
120 | task119,todo
121 | task120,todo
122 | task121,todo
123 | task122,todo
124 | task123,todo
125 | task124,todo
126 | task125,todo
127 | task126,todo
128 | task127,todo
129 | task128,todo
130 | task129,todo
131 | task130,todo
132 | task131,todo
133 | task132,todo
134 | task133,todo
135 | task134,todo
136 | task135,todo
137 | task136,todo
138 | task137,todo
139 | task138,todo
140 | task139,todo
141 | task140,todo
142 | task141,todo
143 | task142,todo
144 | task143,todo
145 | task144,todo
146 | task145,todo
147 | task146,todo
148 | task147,todo
149 | task148,todo
150 | task149,todo
151 | task150,todo
152 | task151,todo
153 | task152,todo
154 | task153,todo
155 | task154,todo
156 | task155,todo
157 | task156,todo
158 | task157,todo
159 | task158,todo
160 | task159,todo
161 | task160,todo
162 | task161,todo
163 | task162,todo
164 | task163,todo
165 | task164,todo
166 | task165,todo
167 | task166,todo
168 | task167,todo
169 | task168,todo
170 | task169,todo
171 | task170,todo
172 | task171,todo
173 | task172,todo
174 | task173,todo
175 | task174,todo
176 | task175,todo
177 | task176,todo
178 | task177,todo
179 | task178,todo
180 | task179,todo
181 | task180,todo
182 | task181,todo
183 | task182,todo
184 | task183,todo
185 | task184,todo
186 | task185,todo
187 | task186,todo
188 | task187,todo
189 | task188,todo
190 | task189,todo
191 | task190,todo
192 | task191,todo
193 | task192,todo
194 | task193,todo
195 | task194,todo
196 | task195,todo
197 | task196,todo
198 | task197,todo
199 | task198,todo
200 | task199,todo
201 | task200,todo
202 | task201,done
203 | task202,done
204 | task203,done
205 | task204,done
206 | task205,done
207 | task206,done
208 | task207,done
209 | task208,done
210 | task209,done
211 | task210,done
212 | task211,done
213 | task212,done
214 | task213,done
215 | task214,done
216 | task215,done
217 | task216,done
218 | task217,done
219 | task218,done
220 | task219,done
221 | task220,done
222 | task221,done
223 | task222,done
224 | task223,done
225 | task224,done
226 | task225,done
227 | task226,done
228 | task227,done
229 | task228,done
230 | task229,done
231 | task230,done
232 | task231,done
233 | task232,done
234 | task233,done
235 | task234,done
236 | task235,done
237 | task236,done
238 | task237,done
239 | task238,done
240 | task239,done
241 | task240,done
242 | task241,done
243 | task242,done
244 | task243,done
245 | task244,done
246 | task245,done
247 | task246,done
248 | task247,done
249 | task248,done
250 | task249,done
251 | task250,done
252 | task251,done
253 | task252,done
254 | task253,done
255 | task254,done
256 | task255,done
257 | task256,done
258 | task257,done
259 | task258,done
260 | task259,done
261 | task260,done
262 | task261,done
263 | task262,done
264 | task263,done
265 | task264,done
266 | task265,done
267 | task266,done
268 | task267,done
269 | task268,done
270 | task269,done
271 | task270,done
272 | task271,done
273 | task272,done
274 | task273,done
275 | task274,done
276 | task275,done
277 | task276,done
278 | task277,done
279 | task278,done
280 | task279,done
281 | task280,done
282 | task281,done
283 | task282,done
284 | task283,done
285 | task284,done
286 | task285,done
287 | task286,done
288 | task287,done
289 | task288,done
290 | task289,done
291 | task290,done
292 | task291,done
293 | task292,done
294 | task293,done
295 | task294,done
296 | task295,done
297 | task296,done
298 | task297,done
299 | task298,done
300 | task299,done
301 | task300,done
302 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # KubeQueue
2 |
3 | [](https://travis-ci.org/yuemori/kube_queue)
4 |
5 | ## Installation
6 |
7 | Add this line to your application's Gemfile:
8 |
9 | ```ruby
10 | gem 'kube_queue'
11 | ```
12 |
13 | And then execute:
14 |
15 | $ bundle
16 |
17 | Or install it yourself as:
18 |
19 | $ gem install kube_queue
20 |
21 | ## Getting Started
22 |
23 | Implement worker:
24 |
25 | ```ruby
26 | class TestWorker
27 | include KubeQueue::Worker
28 |
29 | job_name 'kube-queue-test'
30 | image "my-registry/my-image"
31 | container_name 'kube-queue-test'
32 |
33 | command 'bundle', 'exec', 'kube_queue', 'TestWorker', '-r', './test_worker.rb'
34 |
35 | def perform(payload)
36 | puts payload['message']
37 | end
38 | end
39 | ```
40 |
41 | Setting kubernetes configuration.
42 |
43 | ```ruby
44 | KubeQueue.kubernetes_configure do |client|
45 | client.url = ENV['K8S_URL']
46 | client.ssl_ca_file = ENV['K8S_CA_CERT_FILE']
47 | client.auth_token = File.read(ENV['K8S_TOKEN'])
48 | end
49 | ```
50 |
51 | and run:
52 |
53 | ```ruby
54 | TestWorker.enqueue(message: 'hello')
55 |
56 | # delay
57 | TestWorker.enqueue_at(message: 'hello', Time.now + 100)
58 | ```
59 |
60 | ### ActiveJob Support
61 |
62 | Write to `application.rb`:
63 |
64 | ```ruby
65 | Rails.application.config.active_job.adapter = :kube_queue
66 | ```
67 |
68 | Just put your job into `app/jobs` . Example:
69 |
70 | ```ruby
71 | # app/jobs/print_message_job.rb
72 | class PrintMessageJob < ApplicationJob
73 | include KubeQueue::Worker
74 |
75 | worker_name 'print-message-job'
76 | image "your-registry/your-image"
77 | container_name 'your-container-name'
78 |
79 | def perform(payload)
80 | logger.info payload[:message]
81 | end
82 | end
83 | ```
84 |
85 | and run:
86 |
87 | ```ruby
88 | irb(main):001:0> job = PrintMessageJob.perform_later(message: 'hello, kubernetes!')
89 | Enqueued PrintMessageJob (Job ID: 0bf15b35-62d8-4380-9173-99839ce735ff) to KubeQueue(default) with arguments: {:message=>"hello, kubernetes!"}
90 | => #"hello, kubernetes!"}], @job_id="0bf15b35-62d8-4380-9173-99839ce735ff", @queue_name="default", @priority=nil, @executions=0>
91 | irb(main):002:0> job.status
92 | => #
93 | irb(main):003:0> job.status
94 | => #"Complete", :status=>"True", :lastProbeTime=>"2019-08-12T15:57:03Z", :lastTransitionTime=>"2019-08-12T15:57:03Z"}], startTime="2019-08-12T15:56:37Z", completionTime="2019-08-12T15:57:03Z", succeeded=1>
95 | ```
96 |
97 | See more examples in [here](examples/myapp/app/jobs).
98 |
99 | ### Run job on locally
100 |
101 | ```
102 | bundle exec kube_queue runner JOB_NAME [PAYLOAD]
103 | ```
104 |
105 | See more information by `kube_queue help` or read [here](exe/kube_queue).
106 |
107 | ## Advanced Tips
108 |
109 | ### Get a job status
110 |
111 | ```ruby
112 | job = ComputePiJob.perform_later
113 | job.status
114 | ```
115 |
116 | scheduled job dosent supported now.
117 |
118 | ### Check a generating manifest
119 |
120 | ```ruby
121 | # from class
122 | puts ComputePiJob.manifest
123 |
124 | # from instance
125 | job = ComputePiJob.perform_later
126 | puts job.manifest
127 | ```
128 |
129 | ### Retry job
130 |
131 | Kubernetes Job has a own retry mechanism, if set backoff_limit and/or restart_policy to use it.
132 |
133 | ```ruby
134 | class ComputePiJob
135 | include KubeQueue::Worker
136 |
137 | worker_name 'pi'
138 | image 'perl'
139 | container_name 'pi'
140 | command "perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"
141 |
142 | backoff_limit 10
143 | restart_policy 'Never'
144 | end
145 | ```
146 |
147 | More information, see the official document [here](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#pod-backoff-failure-policy).
148 |
149 | ### Timeout
150 |
151 | Kubernetes Job has a own timeout mechanism, if set the active_deadline_seconds to use it.
152 |
153 | ```ruby
154 | class ComputePiJob
155 | include KubeQueue::Worker
156 |
157 | worker_name 'pi'
158 | image 'perl'
159 | container_name 'pi'
160 | command "perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"
161 |
162 | active_deadline_seconds 300
163 | end
164 | ```
165 |
166 | More information, see the official document [here](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#job-termination-and-cleanup).
167 |
168 | ### Managing container resources
169 |
170 | When you specify a Pod, you can optional specify hou much CPU and memory container needs.
171 |
172 | ```ruby
173 | class ComputePiJob
174 | include KubeQueue::Worker
175 |
176 | worker_name 'pi'
177 | image 'perl'
178 | container_name 'pi'
179 | command "perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"
180 |
181 | cpu_limit '0.3'
182 | cpu_request '0.2'
183 | memory_limit '100m'
184 | memory_request '50m'
185 | end
186 | ```
187 |
188 | More information, see the official document [here](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/).
189 |
190 | ### Use environment variable from ConfigMap/Secret
191 |
192 | ```ruby
193 | class ComputePiJob
194 | include KubeQueue::Worker
195 |
196 | worker_name 'pi'
197 | image 'perl'
198 | container_name 'pi'
199 | command "perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"
200 |
201 | env_from_secret 'mysecret1', 'mysecret2'
202 | env_from_config_map 'myapp'
203 | end
204 | ```
205 |
206 | ## Features
207 |
208 | - Add tests.
209 | - Support multiple kubernetes client configuration.
210 | - Logging informations.
211 | - Support to get CronJob status.
212 |
213 | ## Development(on GCP/GKE)
214 |
215 | setup:
216 |
217 | ```
218 | # create service account and cluster role.
219 | kubectl apply -f examples/k8s/service-account.yaml
220 |
221 | # get ca.crt and token
222 | kubectl get secret -n kube-system kube-queue-test-token-xxx -o jsonpath="{['data']['token']}" | base64 -d > secrets/token
223 | kubectl get secret -n kube-system kube-queue-test-token-xxx -o jsonpath="{['data']['ca\.crt']}" | base64 -d > secrets/ca.crt
224 |
225 | # build image
226 | gcloud builds submit --config cloudbuild.yaml .
227 | ```
228 |
229 | run:
230 |
231 | ```
232 | K8S_URL=https://xx.xxx.xxx.xxx K8S_CA_CERT_FILE=$(pwd)/secrets/ca.crt K8S_TOKEN=$(pwd)/secrets/token IMAGE_NAME=gcr.io/your-project/kube-queue bin/console
233 |
234 | irb(main):001:0> TestWorker.enqueue(message: 'hello, kubernetes!')
235 | ```
236 |
--------------------------------------------------------------------------------
/spec/kube_queue/worker_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'pry-byebug'
3 |
4 | RSpec.describe KubeQueue::Worker do
5 | include ERBh
6 |
7 | describe '.enqueue' do
8 | subject(:job) { PrintMessageJob.enqueue(arg) }
9 |
10 | let(:arg) { { message: 'hello' } }
11 |
12 | before do
13 | expect_any_instance_of(PrintMessageJob).to receive(:perform).with(arg)
14 | expect(KubeQueue.client).to receive(:create_job).and_call_original
15 | end
16 |
17 | it { expect { subject }.not_to raise_error }
18 | it { expect(job.arguments).to eq [arg] }
19 | end
20 |
21 | describe '.enqueue_at' do
22 | subject(:job) { PrintMessageJob.enqueue_at(arg, timestamp) }
23 |
24 | let(:arg) { { message: 'hello' } }
25 | let(:timestamp) { Time.utc(2019, 8, 15, 18, 30, 0).to_i }
26 |
27 | before do
28 | expect_any_instance_of(PrintMessageJob).to receive(:perform).with(arg)
29 | expect(KubeQueue.client).to receive(:create_cron_job).and_call_original
30 | end
31 |
32 | it { expect { subject }.not_to raise_error }
33 | it { expect(job.arguments).to eq [arg] }
34 | it { expect(job.scheduled_at).to eq timestamp }
35 | it { expect(job.resource.spec.schedule).to eq "30 18 15 08 4" }
36 |
37 | context 'when given timezone is not utc' do
38 | let(:timestamp) { Time.new(2019, 8, 15, 18, 30, 0, "+09:00").to_i }
39 |
40 | it { expect(job.resource.spec.schedule).to eq "30 09 15 08 4" }
41 | end
42 | end
43 |
44 | describe '.find' do
45 | let(:payload) { { message: 'hello' } }
46 |
47 | let(:job_manifest) do
48 | YAML.safe_load(erbh(<<~MANIFEST, job_id: job.job_id, payload: JSON.generate([payload], quirks_mode: true)))
49 | apiVersion: batch/v1
50 | kind: Job
51 | metadata:
52 | annotations:
53 | kube-queue-job-class: PrintMessageJob
54 | kube-queue-job-id: <%= @job_id %>
55 | kube-queue-job-payload: '<%= @payload %>'
56 | name: print-message-job-<%= @job_id %>
57 | namespace: default
58 | labels:
59 | kube-queue-job: 'true'
60 | kube-queue-worker-name: print-message-job
61 | kube-queue-job-class: PrintMessageJob
62 | kube-queue-job-id: <%= @job_id %>
63 | spec:
64 | template:
65 | metadata:
66 | annotations:
67 | kube-queue-job-class: PrintMessageJob
68 | kube-queue-job-id: <%= @job_id %>
69 | kube-queue-job-payload: '<%= @payload %>'
70 | labels:
71 | kube-queue-job: 'true'
72 | kube-queue-worker-name: print-message-job
73 | kube-queue-job-class: PrintMessageJob
74 | kube-queue-job-id: <%= @job_id %>
75 | spec:
76 | containers:
77 | - name: ruby
78 | image: ruby
79 | command:
80 | - bundle
81 | - exec
82 | - kube_queue
83 | - runner
84 | - PrintMessageJob
85 | env:
86 | - name: KUBE_QUEUE_MESSAGE_PAYLOAD
87 | value: '<%= @payload %>'
88 | resources: {}
89 | restartPolicy: Never
90 | MANIFEST
91 | end
92 |
93 | let(:resource) { K8s::Resource.new(job_manifest) }
94 |
95 | before do
96 | expect(KubeQueue.client).to receive(:get_job).and_return(resource)
97 | end
98 |
99 | subject { PrintMessageJob.find(job.job_id) }
100 |
101 | let(:job) { PrintMessageJob.new(payload) }
102 |
103 | it { expect(subject.job_id).to eq job.job_id }
104 | it { expect(subject.resource).to eq resource }
105 |
106 | context 'when error raised' do
107 | before { expect(JSON).to receive(:parse).and_raise(JSON::ParserError) }
108 |
109 | it { expect { subject }.to raise_error JSON::ParserError }
110 | end
111 | end
112 |
113 | describe '#manifest' do
114 | subject { job.manifest }
115 |
116 | let(:job) { PrintMessageJob.new(message: 'hello') }
117 |
118 | let(:job_manifest) do
119 | YAML.safe_load(erbh(<<~MANIFEST, job_id: job.job_id, payload: JSON.generate([{ message: 'hello' }], quirks_mode: true)))
120 | ---
121 | apiVersion: batch/v1
122 | kind: Job
123 | metadata:
124 | annotations:
125 | kube-queue-job-class: PrintMessageJob
126 | kube-queue-job-id: <%= @job_id %>
127 | kube-queue-job-payload: '<%= @payload %>'
128 | name: print-message-job-<%= @job_id %>
129 | namespace: default
130 | labels:
131 | kube-queue-job: 'true'
132 | kube-queue-worker-name: print-message-job
133 | kube-queue-job-class: PrintMessageJob
134 | kube-queue-job-id: <%= @job_id %>
135 | spec:
136 | template:
137 | metadata:
138 | annotations:
139 | kube-queue-job-class: PrintMessageJob
140 | kube-queue-job-id: <%= @job_id %>
141 | kube-queue-job-payload: '<%= @payload %>'
142 | labels:
143 | kube-queue-job: 'true'
144 | kube-queue-worker-name: print-message-job
145 | kube-queue-job-class: PrintMessageJob
146 | kube-queue-job-id: <%= @job_id %>
147 | spec:
148 | containers:
149 | - name: ruby
150 | image: ruby
151 | command:
152 | - bundle
153 | - exec
154 | - kube_queue
155 | - runner
156 | - PrintMessageJob
157 | env:
158 | - name: KUBE_QUEUE_MESSAGE_PAYLOAD
159 | value: '<%= @payload %>'
160 | resources: {}
161 | restartPolicy: Never
162 | MANIFEST
163 | end
164 |
165 | context 'when job' do
166 | it { is_expected.to eq job_manifest }
167 | end
168 |
169 | context 'when cronjob' do
170 | before { job.scheduled_at = timestamp }
171 |
172 | let(:timestamp) { Time.utc(2019, 8, 15, 18, 30).to_i }
173 |
174 | let(:cron_job_manifest) do
175 | {
176 | apiVersion: "batch/v1beta1",
177 | kind: "CronJob",
178 | metadata: job_manifest["metadata"],
179 | spec: {
180 | startingDeadlineSeconds: nil,
181 | concurrentPolicy: "Allow",
182 | schedule: "30 18 15 08 4",
183 | jobTemplate: {
184 | spec: job_manifest["spec"]
185 | }
186 | }
187 | }
188 | end
189 |
190 | it { is_expected.to eq cron_job_manifest }
191 | end
192 | end
193 | end
194 |
--------------------------------------------------------------------------------
/examples/myapp/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ../..
3 | specs:
4 | kube_queue (0.3.0)
5 | k8s-client
6 | thor
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actioncable (5.2.3)
12 | actionpack (= 5.2.3)
13 | nio4r (~> 2.0)
14 | websocket-driver (>= 0.6.1)
15 | actionmailer (5.2.3)
16 | actionpack (= 5.2.3)
17 | actionview (= 5.2.3)
18 | activejob (= 5.2.3)
19 | mail (~> 2.5, >= 2.5.4)
20 | rails-dom-testing (~> 2.0)
21 | actionpack (5.2.3)
22 | actionview (= 5.2.3)
23 | activesupport (= 5.2.3)
24 | rack (~> 2.0)
25 | rack-test (>= 0.6.3)
26 | rails-dom-testing (~> 2.0)
27 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
28 | actionview (5.2.3)
29 | activesupport (= 5.2.3)
30 | builder (~> 3.1)
31 | erubi (~> 1.4)
32 | rails-dom-testing (~> 2.0)
33 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
34 | activejob (5.2.3)
35 | activesupport (= 5.2.3)
36 | globalid (>= 0.3.6)
37 | activemodel (5.2.3)
38 | activesupport (= 5.2.3)
39 | activerecord (5.2.3)
40 | activemodel (= 5.2.3)
41 | activesupport (= 5.2.3)
42 | arel (>= 9.0)
43 | activestorage (5.2.3)
44 | actionpack (= 5.2.3)
45 | activerecord (= 5.2.3)
46 | marcel (~> 0.3.1)
47 | activesupport (5.2.3)
48 | concurrent-ruby (~> 1.0, >= 1.0.2)
49 | i18n (>= 0.7, < 2)
50 | minitest (~> 5.1)
51 | tzinfo (~> 1.1)
52 | addressable (2.6.0)
53 | public_suffix (>= 2.0.2, < 4.0)
54 | archive-zip (0.12.0)
55 | io-like (~> 0.3.0)
56 | arel (9.0.0)
57 | bindex (0.8.1)
58 | bootsnap (1.4.4)
59 | msgpack (~> 1.0)
60 | builder (3.2.3)
61 | byebug (11.0.1)
62 | capybara (3.28.0)
63 | addressable
64 | mini_mime (>= 0.1.3)
65 | nokogiri (~> 1.8)
66 | rack (>= 1.6.0)
67 | rack-test (>= 0.6.3)
68 | regexp_parser (~> 1.5)
69 | xpath (~> 3.2)
70 | childprocess (1.0.1)
71 | rake (< 13.0)
72 | chromedriver-helper (2.1.1)
73 | archive-zip (~> 0.10)
74 | nokogiri (~> 1.8)
75 | coderay (1.1.2)
76 | coffee-rails (4.2.2)
77 | coffee-script (>= 2.2.0)
78 | railties (>= 4.0.0)
79 | coffee-script (2.4.1)
80 | coffee-script-source
81 | execjs
82 | coffee-script-source (1.12.2)
83 | concurrent-ruby (1.1.5)
84 | crass (1.0.5)
85 | dry-configurable (0.9.0)
86 | concurrent-ruby (~> 1.0)
87 | dry-core (~> 0.4, >= 0.4.7)
88 | dry-container (0.7.2)
89 | concurrent-ruby (~> 1.0)
90 | dry-configurable (~> 0.1, >= 0.1.3)
91 | dry-core (0.4.9)
92 | concurrent-ruby (~> 1.0)
93 | dry-equalizer (0.3.0)
94 | dry-inflector (0.2.0)
95 | dry-logic (0.6.1)
96 | concurrent-ruby (~> 1.0)
97 | dry-core (~> 0.2)
98 | dry-equalizer (~> 0.2)
99 | dry-struct (0.5.1)
100 | dry-core (~> 0.4, >= 0.4.3)
101 | dry-equalizer (~> 0.2)
102 | dry-types (~> 0.13)
103 | ice_nine (~> 0.11)
104 | dry-types (0.13.4)
105 | concurrent-ruby (~> 1.0)
106 | dry-container (~> 0.3)
107 | dry-core (~> 0.4, >= 0.4.4)
108 | dry-equalizer (~> 0.2)
109 | dry-inflector (~> 0.1, >= 0.1.2)
110 | dry-logic (~> 0.4, >= 0.4.2)
111 | erubi (1.8.0)
112 | excon (0.71.0)
113 | execjs (2.7.0)
114 | ffi (1.11.1)
115 | globalid (0.4.2)
116 | activesupport (>= 4.2.0)
117 | hashdiff (1.0.0)
118 | i18n (1.6.0)
119 | concurrent-ruby (~> 1.0)
120 | ice_nine (0.11.2)
121 | io-like (0.3.0)
122 | jbuilder (2.9.1)
123 | activesupport (>= 4.2.0)
124 | jsonpath (0.9.9)
125 | multi_json
126 | to_regexp (~> 0.2.1)
127 | k8s-client (0.10.4)
128 | dry-struct (~> 0.5.0)
129 | dry-types (~> 0.13.0)
130 | excon (~> 0.66)
131 | hashdiff (~> 1.0.0)
132 | jsonpath (~> 0.9.5)
133 | recursive-open-struct (~> 1.1.0)
134 | yajl-ruby (~> 1.4.0)
135 | yaml-safe_load_stream (~> 0.1)
136 | kaminari (1.1.1)
137 | activesupport (>= 4.1.0)
138 | kaminari-actionview (= 1.1.1)
139 | kaminari-activerecord (= 1.1.1)
140 | kaminari-core (= 1.1.1)
141 | kaminari-actionview (1.1.1)
142 | actionview
143 | kaminari-core (= 1.1.1)
144 | kaminari-activerecord (1.1.1)
145 | activerecord
146 | kaminari-core (= 1.1.1)
147 | kaminari-core (1.1.1)
148 | libv8 (7.3.492.27.1)
149 | listen (3.1.5)
150 | rb-fsevent (~> 0.9, >= 0.9.4)
151 | rb-inotify (~> 0.9, >= 0.9.7)
152 | ruby_dep (~> 1.2)
153 | loofah (2.3.1)
154 | crass (~> 1.0.2)
155 | nokogiri (>= 1.5.9)
156 | mail (2.7.1)
157 | mini_mime (>= 0.1.1)
158 | marcel (0.3.3)
159 | mimemagic (~> 0.3.2)
160 | method_source (0.9.2)
161 | mimemagic (0.3.3)
162 | mini_mime (1.0.2)
163 | mini_portile2 (2.4.0)
164 | mini_racer (0.2.6)
165 | libv8 (>= 6.9.411)
166 | minitest (5.11.3)
167 | msgpack (1.3.1)
168 | multi_json (1.14.1)
169 | mysql2 (0.5.2)
170 | nio4r (2.4.0)
171 | nokogiri (1.10.5)
172 | mini_portile2 (~> 2.4.0)
173 | pry (0.12.2)
174 | coderay (~> 1.1.0)
175 | method_source (~> 0.9.0)
176 | pry-byebug (3.7.0)
177 | byebug (~> 11.0)
178 | pry (~> 0.10)
179 | pry-rails (0.3.9)
180 | pry (>= 0.10.4)
181 | public_suffix (3.1.1)
182 | puma (4.3.1)
183 | nio4r (~> 2.0)
184 | rack (2.0.8)
185 | rack-test (1.1.0)
186 | rack (>= 1.0, < 3)
187 | rails (5.2.3)
188 | actioncable (= 5.2.3)
189 | actionmailer (= 5.2.3)
190 | actionpack (= 5.2.3)
191 | actionview (= 5.2.3)
192 | activejob (= 5.2.3)
193 | activemodel (= 5.2.3)
194 | activerecord (= 5.2.3)
195 | activestorage (= 5.2.3)
196 | activesupport (= 5.2.3)
197 | bundler (>= 1.3.0)
198 | railties (= 5.2.3)
199 | sprockets-rails (>= 2.0.0)
200 | rails-dom-testing (2.0.3)
201 | activesupport (>= 4.2.0)
202 | nokogiri (>= 1.6)
203 | rails-html-sanitizer (1.2.0)
204 | loofah (~> 2.2, >= 2.2.2)
205 | railties (5.2.3)
206 | actionpack (= 5.2.3)
207 | activesupport (= 5.2.3)
208 | method_source
209 | rake (>= 0.8.7)
210 | thor (>= 0.19.0, < 2.0)
211 | rake (12.3.3)
212 | rb-fsevent (0.10.3)
213 | rb-inotify (0.10.0)
214 | ffi (~> 1.0)
215 | recursive-open-struct (1.1.0)
216 | regexp_parser (1.6.0)
217 | ruby_dep (1.5.0)
218 | rubyzip (1.3.0)
219 | sass (3.7.4)
220 | sass-listen (~> 4.0.0)
221 | sass-listen (4.0.0)
222 | rb-fsevent (~> 0.9, >= 0.9.4)
223 | rb-inotify (~> 0.9, >= 0.9.7)
224 | sass-rails (5.0.7)
225 | railties (>= 4.0.0, < 6)
226 | sass (~> 3.1)
227 | sprockets (>= 2.8, < 4.0)
228 | sprockets-rails (>= 2.0, < 4.0)
229 | tilt (>= 1.1, < 3)
230 | selenium-webdriver (3.142.3)
231 | childprocess (>= 0.5, < 2.0)
232 | rubyzip (~> 1.2, >= 1.2.2)
233 | spring (2.1.0)
234 | spring-watcher-listen (2.0.1)
235 | listen (>= 2.7, < 4.0)
236 | spring (>= 1.2, < 3.0)
237 | sprockets (3.7.2)
238 | concurrent-ruby (~> 1.0)
239 | rack (> 1, < 3)
240 | sprockets-rails (3.2.1)
241 | actionpack (>= 4.0)
242 | activesupport (>= 4.0)
243 | sprockets (>= 3.0.0)
244 | thor (0.20.3)
245 | thread_safe (0.3.6)
246 | tilt (2.0.9)
247 | to_regexp (0.2.1)
248 | turbolinks (5.2.0)
249 | turbolinks-source (~> 5.2)
250 | turbolinks-source (5.2.0)
251 | tzinfo (1.2.5)
252 | thread_safe (~> 0.1)
253 | uglifier (4.1.20)
254 | execjs (>= 0.3.0, < 3)
255 | web-console (3.7.0)
256 | actionview (>= 5.0)
257 | activemodel (>= 5.0)
258 | bindex (>= 0.4.0)
259 | railties (>= 5.0)
260 | websocket-driver (0.7.1)
261 | websocket-extensions (>= 0.1.0)
262 | websocket-extensions (0.1.4)
263 | xpath (3.2.0)
264 | nokogiri (~> 1.8)
265 | yajl-ruby (1.4.1)
266 | yaml-safe_load_stream (0.1.1)
267 |
268 | PLATFORMS
269 | ruby
270 |
271 | DEPENDENCIES
272 | bootsnap (>= 1.1.0)
273 | byebug
274 | capybara (>= 2.15)
275 | chromedriver-helper
276 | coffee-rails (~> 4.2)
277 | jbuilder (~> 2.5)
278 | kaminari
279 | kube_queue!
280 | listen (>= 3.0.5, < 3.2)
281 | mini_racer
282 | mysql2
283 | pry-byebug
284 | pry-rails
285 | puma (~> 4.3)
286 | rails (~> 5.2.3)
287 | sass-rails (~> 5.0)
288 | selenium-webdriver
289 | spring
290 | spring-watcher-listen (~> 2.0.0)
291 | turbolinks (~> 5)
292 | uglifier (>= 1.3.0)
293 | web-console (>= 3.3.0)
294 |
295 | BUNDLED WITH
296 | 2.0.2
297 |
--------------------------------------------------------------------------------