kube_queue 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1511d8cfdd1b8fb8375d4162e389750b4372bc19
4
- data.tar.gz: 963ace432f1420c863d94d491e81611fa7141279
2
+ SHA256:
3
+ metadata.gz: 1c864d0acb58ed1b538edeebcdec417d6284b9a4df1d151a9c008eb190e9b9f3
4
+ data.tar.gz: 49d9aff0aca53a00836f9358149741694173b19df4ea1e16aef40ff0a23441df
5
5
  SHA512:
6
- metadata.gz: 92c9790d5954b947e494e408556ba5cf5ddac75c2f5c4dc70b094833f2afdd24d4e565bcb3da86951c070283df1451d73d14dc672b74103cddfea40639b557b9
7
- data.tar.gz: 3932e801e465f3d5b7427dfd027af5b0680c8fffc338ab7f8abd151ca86010617386f00b35941a604436c94fc638e0b1aa4192f5f7198758cbe30caae7959170
6
+ metadata.gz: af4c1d02900ac0e864e59ee4fa81dcb43771ddafcdd50bf2dfc4956f2e37b04f121ca6bf13c7be185d2876f74ab582425022fe687f7ca9993e4261fd6b65bedb
7
+ data.tar.gz: 314585e098b2578c124ce28b3accf08e6f51c05058a0b2590ee5ca47c65ef0b58cabe6c5e105372693c986d6c2dc1ec51a8c3fb5a4052323f11ce4897419528d
data/.gcloudignore CHANGED
@@ -1 +1,4 @@
1
1
  !.git
2
+ secrets
3
+ examples/myapp/tmp/*
4
+ examples/myapp/log/*
data/.rubocop.yml CHANGED
@@ -63,3 +63,6 @@ Metrics/BlockLength:
63
63
  Style/MixinUsage:
64
64
  Exclude:
65
65
  - 'spec/**/*.rb'
66
+
67
+ Layout/AlignArguments:
68
+ Enabled: false
data/Dockerfile CHANGED
@@ -5,13 +5,13 @@ WORKDIR /app
5
5
  RUN apt-get update -y && apt-get install git --no-install-recommends && rm -r /var/cache/apt /var/lib/apt/lists
6
6
 
7
7
  RUN gem install bundler:2.0.2
8
- COPY docker/Gemfile Gemfile
8
+ COPY examples/docker/Gemfile Gemfile
9
9
  COPY Gemfile kube_queue.gemspec .git /vendor/kube_queue/
10
10
  COPY exe/kube_queue /vendor/kube_queue/exe/kube_queue
11
11
  COPY lib/kube_queue/version.rb /vendor/kube_queue/lib/kube_queue/version.rb
12
12
  RUN bundle install -j4
13
13
 
14
- COPY docker/test_worker.rb .
14
+ COPY examples/docker/test_worker.rb .
15
15
  COPY . /vendor/kube_queue
16
16
 
17
17
  CMD ["bundle", "exec", "kube_queue", "TestWorker", "-r", "./test_worker.rb"]
data/Dockerfile.myapp ADDED
@@ -0,0 +1,22 @@
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 examples/myapp/ $WORKDIR
21
+
22
+ CMD ["bundle", "exec", "rails", "console"]
data/README.md CHANGED
@@ -1,13 +1,5 @@
1
1
  # KubeQueue
2
2
 
3
- ## Features
4
-
5
- - ActiveJob integration
6
- - Support multiple kubernetes client configuration.
7
- - Support templating and customization for kubernetes job manifest.
8
- - Job dosen't returns id. Can not track job details from code.
9
- - Logging
10
-
11
3
  ## Installation
12
4
 
13
5
  Add this line to your application's Gemfile:
@@ -44,29 +36,86 @@ class TestWorker
44
36
  end
45
37
  ```
46
38
 
47
- Setting kubernetes configuration and run:
39
+ Setting kubernetes configuration.
48
40
 
49
- ```
41
+ ```ruby
50
42
  KubeQueue.kubernetes_configure do |client|
51
43
  client.url = ENV['K8S_URL']
52
44
  client.ssl_ca_file = ENV['K8S_CA_CERT_FILE']
53
45
  client.auth_token = File.read(ENV['K8S_TOKEN'])
54
46
  end
47
+ ```
48
+
49
+ and run:
55
50
 
51
+ ```ruby
56
52
  TestWorker.perform(message: 'hello')
57
53
  ```
58
54
 
59
- ## Development
55
+ ### ActiveJob Support
56
+
57
+ Write to `application.rb`:
58
+
59
+ ```ruby
60
+ Rails.application.config.active_job.adapter = :kube_queue
61
+ ```
62
+
63
+ Just put your job into `app/jobs` . Example:
64
+
65
+ ```ruby
66
+ # app/jobs/print_message_job.rb
67
+ class PrintMessageJob < ApplicationJob
68
+ include KubeQueue::Worker
69
+
70
+ worker_name 'print-message-job'
71
+ image "your-registry/your-image"
72
+ container_name 'your-container-name'
73
+
74
+ def perform(payload)
75
+ logger.info payload[:message]
76
+ end
77
+ end
78
+ ```
79
+
80
+ and run:
81
+
82
+ ```ruby
83
+ irb(main):001:0> job = PrintMessageJob.perform_later(message: 'hello, kubernetes!')
84
+ Enqueued PrintMessageJob (Job ID: 0bf15b35-62d8-4380-9173-99839ce735ff) to KubeQueue(default) with arguments: {:message=>"hello, kubernetes!"}
85
+ => #<PrintMessageJob:0x00007fbfd00c7848 @arguments=[{:message=>"hello, kubernetes!"}], @job_id="0bf15b35-62d8-4380-9173-99839ce735ff", @queue_name="default", @priority=nil, @executions=0>
86
+ irb(main):002:0> job.status
87
+ => #<K8s::Resource startTime="2019-08-12T15:56:37Z", active=1>
88
+ irb(main):003:0> job.status
89
+ => #<K8s::Resource conditions=[{:type=>"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>
90
+ ```
91
+
92
+ See more examples in [here](examples).
93
+
94
+ ### Run job on locally
95
+
96
+ ```
97
+ bundle exec kube_queue runner JOB_NAME [PAYLOAD]
98
+ ```
99
+
100
+ See more information by `kube_queue help` or [here](exe/kube_queue).
101
+
102
+ ## Features
103
+
104
+ - Add tests.
105
+ - Support multiple kubernetes client configuration.
106
+ - Logging informations.
107
+
108
+ ## Development(on GCP/GKE)
60
109
 
61
110
  setup:
62
111
 
63
112
  ```
64
113
  # create service account and cluster role.
65
- kubectl apply -f k8s/service-account.yaml
114
+ kubectl apply -f examples/k8s/service-account.yaml
66
115
 
67
116
  # get ca.crt and token
68
- < k get secret -n kube-system kube-queue-test-token-xxx -o jsonpath="{['data']['token']}" | base64 -d > secrets/token
69
- < k get secret -n kube-system kube-queue-test-token-xxx -o jsonpath="{['data']['ca\.crt']}" | base64 -d > secrets/ca.crt
117
+ kubectl get secret -n kube-system kube-queue-test-token-xxx -o jsonpath="{['data']['token']}" | base64 -d > secrets/token
118
+ kubectl get secret -n kube-system kube-queue-test-token-xxx -o jsonpath="{['data']['ca\.crt']}" | base64 -d > secrets/ca.crt
70
119
 
71
120
  # build image
72
121
  gcloud builds submit --config cloudbuild.yaml .
@@ -77,5 +126,5 @@ run:
77
126
  ```
78
127
  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
79
128
 
80
- irb(main):001:0> TestWorker.perform_async(message: 'hello kubernetes')
129
+ irb(main):001:0> TestWorker.perform_async(message: 'hello, kubernetes!')
81
130
  ```
data/bin/console CHANGED
@@ -16,7 +16,7 @@ require "irb"
16
16
  KubeQueue.kubernetes_configure do |client|
17
17
  client.url = ENV['K8S_URL']
18
18
  client.ssl_ca_file = ENV['K8S_CA_CERT_FILE']
19
- client.auth_token = File.read(ENV['K8S_TOKEN'])
19
+ client.auth_token = ENV['K8S_TOKEN']
20
20
  end
21
21
 
22
22
  IRB.start(__FILE__)
data/cloudbuild.yaml CHANGED
@@ -1,5 +1,9 @@
1
- images: ['gcr.io/$PROJECT_ID/kube-queue:latest']
1
+ images: ['gcr.io/$PROJECT_ID/kube-queue:latest', 'gcr.io/$PROJECT_ID/kube-queue-test-app:latest']
2
2
 
3
3
  steps:
4
4
  - name: 'gcr.io/cloud-builders/docker'
5
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: ['-']
data/exe/kube_queue CHANGED
@@ -1,14 +1,63 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../lib/kube_queue/cli'
3
+ require 'rubygems'
4
+ require 'thor'
5
+ require 'kube_queue'
4
6
 
5
- begin
6
- cli = KubeQueue::CLI.new(ARGV)
7
- cli.run
8
- rescue StandardError => e
9
- raise e if $DEBUG
7
+ module KubeQueue
8
+ class CLI < Thor
9
+ default_task :version
10
10
 
11
- STDERR.puts e.message
12
- STDERR.puts e.backtrace.join("\n")
13
- exit 1
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
14
61
  end
62
+
63
+ KubeQueue::CLI.start
data/kube_queue.gemspec CHANGED
@@ -18,13 +18,14 @@ Gem::Specification.new do |spec|
18
18
  spec.metadata["changelog_uri"] = "https://github.com/yuemori/kube_queue/CHANGELOG.md"
19
19
 
20
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)/}) }
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|examples|cloudbuild.yaml)/}) }
22
22
  end
23
23
  spec.bindir = "exe"
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ["lib"]
26
26
 
27
27
  spec.add_dependency "k8s-client"
28
+ spec.add_dependency "thor"
28
29
  spec.add_development_dependency "bundler", "~> 2.0"
29
30
  spec.add_development_dependency "rake", "~> 10.0"
30
31
  spec.add_development_dependency "rspec", "~> 3.0"
@@ -0,0 +1,31 @@
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, ActiveJob::Arguments.serialize(job.arguments))
14
+ end
15
+
16
+ def enqueue_at(_job, _timestamp)
17
+ raise NotImplementedError
18
+ end
19
+ end
20
+
21
+ # Interface for ActiveJob 5.0
22
+ def enqueue(job)
23
+ KubeQueueAdapter.enqueue(job)
24
+ end
25
+
26
+ def enqueue_at(job, timestamp)
27
+ KubeQueueAdapter.enqueue_at(job, timestamp)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -5,8 +5,17 @@ module KubeQueue
5
5
  class Client
6
6
  def create_job(manifest)
7
7
  job = K8s::Resource.new(manifest)
8
- job.metadata.namespace = 'default'
9
- client.api('batch/v1').resource('jobs').create_resource(job)
8
+ job.metadata.namespace ||= 'default'
9
+ client.resource('jobs').create_resource(job)
10
+ end
11
+
12
+ def get_job(namespace, name)
13
+ client.resource('jobs', namespace: namespace).get(name)
14
+ end
15
+
16
+ def list_job(namespace = nil)
17
+ selector = { 'kube-queue-job': 'true' }
18
+ client.resource('jobs', namespace: namespace).list(labelSelector: selector)
10
19
  end
11
20
 
12
21
  attr_accessor :url, :ssl_ca_file, :auth_token
@@ -14,7 +23,7 @@ module KubeQueue
14
23
  private
15
24
 
16
25
  def client
17
- @client ||= K8s.client(url, ssl_ca_file: ssl_ca_file, auth_token: auth_token)
26
+ @client ||= K8s.client(url, ssl_ca_file: ssl_ca_file, auth_token: auth_token).api('batch/v1')
18
27
  end
19
28
  end
20
29
  end
@@ -1,10 +1,9 @@
1
- require 'erb'
2
- require 'json'
1
+ require 'kube_queue/manifest_builder'
3
2
 
4
3
  module KubeQueue
5
4
  class Executor
6
- def perform_async(job, body)
7
- manifest = job.build_manifest(body)
5
+ def enqueue(job, payload)
6
+ manifest = ManifestBuilder.new(job, payload).build
8
7
  KubeQueue.client.create_job(manifest)
9
8
  end
10
9
  end
@@ -0,0 +1,64 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+
4
+ module KubeQueue
5
+ class JobSpecification
6
+ class MissingParameterError < StandardError; end
7
+
8
+
9
+ attr_reader :job_class
10
+
11
+ attr_accessor :payload, :name, :active_deadline_seconds, :backoff_limit
12
+
13
+ attr_writer :image, :namespace, :worker_name, :command,
14
+ :container_name, :restart_policy, :job_labels, :pod_labels
15
+
16
+ def initialize(job_class)
17
+ @job_class = job_class
18
+ end
19
+
20
+ def job_name(job_id)
21
+ "#{worker_name}-#{job_id}"
22
+ end
23
+
24
+ def image
25
+ @image || raise_not_found_required_parameter('image')
26
+ end
27
+
28
+ def namespace
29
+ @namespace || 'default'
30
+ end
31
+
32
+ def worker_name
33
+ @worker_name || raise_not_found_required_parameter('worker_name')
34
+ end
35
+
36
+ def container_name
37
+ @container_name || worker_name
38
+ end
39
+
40
+ def command
41
+ @command || ['bundle', 'exec', 'kube_queue', 'runner', job_class.name]
42
+ end
43
+
44
+ def restart_policy
45
+ @restart_policy || 'Never'
46
+ end
47
+
48
+ def job_labels
49
+ @job_labels || {}
50
+ end
51
+
52
+ def pod_labels
53
+ @pod_labels || {}
54
+ end
55
+
56
+ def env
57
+ KubeQueue.default_env.merge(@env || {})
58
+ end
59
+
60
+ def raise_not_found_required_parameter(field)
61
+ raise MissingParameterError, "#{field} is required"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,25 @@
1
+ require 'erb'
2
+ require 'json'
3
+
4
+ module KubeQueue
5
+ class ManifestBuilder
6
+ attr_reader :job
7
+
8
+ def initialize(job, payload = nil)
9
+ @job = job
10
+ @payload = payload
11
+ end
12
+
13
+ def spec
14
+ job.job_spec
15
+ end
16
+
17
+ def payload
18
+ @payload ? JSON.generate(@payload, quirks_mode: true) : nil
19
+ end
20
+
21
+ def build
22
+ YAML.safe_load(ERB.new(job.template, nil, "-").result(binding))
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ ActiveSupport.on_load(:active_job) do
2
+ require "active_job/adapters/kube_queue_adapter"
3
+ end
@@ -1,3 +1,3 @@
1
1
  module KubeQueue
2
- VERSION = "0.2.0".freeze
2
+ VERSION = "0.3.0".freeze
3
3
  end
@@ -0,0 +1,49 @@
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
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
+ end
48
+ end
49
+ end
@@ -1,6 +1,5 @@
1
- require 'erb'
2
- require 'yaml'
3
- require 'kube_queue/job_configuration'
1
+ require 'kube_queue/worker/dsl'
2
+ require 'kube_queue/job_specification'
4
3
 
5
4
  module KubeQueue
6
5
  module Worker
@@ -11,57 +10,55 @@ module KubeQueue
11
10
  end
12
11
 
13
12
  module ClassMethods
14
- def job_name(name)
15
- @job_name = name
16
- end
13
+ include DSL
17
14
 
18
- def container_name(container_name)
19
- @container_name = container_name
15
+ def enqueue(body = nil)
16
+ KubeQueue.executor.enqueue(new, body)
20
17
  end
18
+ alias_method :perform_async, :enqueue
21
19
 
22
- def image(image)
23
- @image = image
20
+ def enqueue_at(body = nil)
21
+ KubeQueue.executor.enqueue(new, body)
24
22
  end
25
23
 
26
- def command(*command)
27
- @command = command
24
+ def read_template
25
+ File.read(@template || File.expand_path('../../../template/job.yaml', __FILE__))
28
26
  end
27
+ end
29
28
 
30
- def template(template)
31
- @template = template
32
- end
29
+ def template
30
+ self.class.read_template
31
+ end
33
32
 
34
- def restart_policy(policy)
35
- @restart_policy = policy
36
- end
33
+ def job_spec
34
+ self.class.job_spec
35
+ end
37
36
 
38
- def backoff_limit(limit)
39
- @backoff_limit = limit
40
- end
37
+ attr_accessor :job_id, :arguments
41
38
 
42
- def build_manifest(body)
43
- spec = JobSpecification.new.configure do |s|
44
- s.id = SecureRandom.uuid
45
- s.image = @image
46
- s.name = name
47
- s.command = @command
48
- s.job_name = @job_name
49
- s.container_name = @job_name
50
- s.template = @template
51
- s.backoff_limit = @backoff_limit
52
- s.payload = JSON.generate(body, quirks_mode: true)
53
- end
39
+ def initialize(*arguments)
40
+ # Compatibility for ActiveJob interface
41
+ super
54
42
 
55
- spec.to_manifest
56
- end
43
+ @arguments = arguments
44
+ @job_id = SecureRandom.uuid
45
+ end
57
46
 
58
- def perform_sync(body)
59
- new.perform(body)
60
- end
47
+ def perform_now
48
+ # Compatibility for ActiveJob interface
49
+ return super if defined?(super)
61
50
 
62
- def perform_async(body)
63
- KubeQueue.executor.perform_async(self, body)
64
- end
51
+ perform(*arguments)
52
+ end
53
+
54
+ def perform(*)
55
+ raise NotImplementedError
56
+ end
57
+
58
+ # FIXME: improve performance
59
+ def status
60
+ res = KubeQueue.client.get_job(job_spec.namespace, job_spec.job_name(job_id))
61
+ res.status
65
62
  end
66
63
  end
67
64
  end
data/lib/kube_queue.rb CHANGED
@@ -3,9 +3,10 @@ require "kube_queue/executor"
3
3
  require "kube_queue/configuration"
4
4
  require "kube_queue/worker"
5
5
  require "kube_queue/client"
6
+ require "active_job/adapters/kube_queue_adapter" if defined?(Rails)
6
7
 
7
8
  module KubeQueue
8
- class Error < StandardError; end
9
+ class JobNotFound < StandardError; end
9
10
 
10
11
  class << self
11
12
  attr_writer :executor
@@ -34,6 +35,31 @@ module KubeQueue
34
35
  @configuration ||= Configuration.new
35
36
  end
36
37
 
38
+ def list(namespace = nil)
39
+ client.list_job(namespace).map do |job|
40
+ worker = fetch_worker(job.metadata.annotations['kube-queue-job-class'])
41
+ job_id = job.metadata.annotations['kube-queue-job-id']
42
+ payload = deserialize(job.metadata.annotations['kube-queue-message-payload'])
43
+
44
+ job = worker.new(*payload)
45
+ job.job_id = job_id
46
+ job
47
+ end
48
+ end
49
+
50
+ attr_writer :default_env
51
+
52
+ def default_env
53
+ return @default_env if @default_env
54
+
55
+ return {} unless defined?(Rails)
56
+
57
+ {
58
+ RAILS_LOG_TO_STDOUT: ENV['RAILS_LOG_TO_STDOUT'],
59
+ RAILS_ENV: ENV['RAILS_ENV']
60
+ }
61
+ end
62
+
37
63
  def fetch_worker(name)
38
64
  worker_registry.fetch(name)
39
65
  end
@@ -45,5 +71,29 @@ module KubeQueue
45
71
  def worker_registry
46
72
  @worker_registry ||= {}
47
73
  end
74
+
75
+ private
76
+
77
+ def deserialize(payload)
78
+ return payload if payload.blank?
79
+
80
+ payload = JSON.parse(payload)
81
+ # Compatibility for ActiveJob serialized payload
82
+ payload = [payload] unless payload.is_a?(Array)
83
+
84
+ if defined?(ActiveJob::Arguments)
85
+ begin
86
+ payload = ActiveJob::Arguments.deserialize(payload)
87
+ rescue ActiveJob::DeserializationError => e
88
+ logger.warn e.message
89
+ logger.warn "#{payload} can not deserialized"
90
+ end
91
+ end
92
+
93
+ payload
94
+ rescue JSON::ParseError => e
95
+ logger.warn e.message
96
+ logger.warn "#{payload} can not deserialized"
97
+ end
48
98
  end
49
99
  end
data/template/job.yaml CHANGED
@@ -1,16 +1,44 @@
1
1
  apiVersion: batch/v1
2
2
  kind: Job
3
3
  metadata:
4
- name: "<%= job_name %>-<%= id %>"
4
+ annotations:
5
+ kube-queue-message-payload: '<%= payload %>'
6
+ kube-queue-job-class: "<%= spec.job_class %>"
7
+ kube-queue-job-id: "<%= job.job_id %>"
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 %>"
14
+ kube-queue-job-id: "<%= job.job_id %>"
5
15
  spec:
6
16
  template:
17
+ metadata:
18
+ annotations:
19
+ kube-queue-message-payload: '<%= payload %>'
20
+ kube-queue-job-class: "<%= spec.job_class %>"
21
+ kube-queue-job-id: "<%= job.job_id %>"
22
+ labels:
23
+ kube-queue-job: "true"
24
+ kube-queue-worker-name: "<%= spec.worker_name %>"
25
+ kube-queue-job-class: "<%= spec.job_class %>"
26
+ kube-queue-job-id: "<%= job.job_id %>"
27
+ <%- spec.job_labels.each do |key, value| %>
28
+ <%= key %>: "<%= value %>"
29
+ <%- end %>
7
30
  spec:
8
31
  containers:
9
- - name: "<%= container_name %>"
10
- image: "<%= image %>"
11
- command: <%= command %>
32
+ - name: "<%= spec.container_name %>"
33
+ image: "<%= spec.image %>"
34
+ command: <%= spec.command %>
12
35
  env:
13
36
  - name: "KUBE_QUEUE_MESSAGE_PAYLOAD"
14
37
  value: '<%= payload %>'
15
- restartPolicy: "<%= restart_policy %>"
16
- backoffLimit: <%= backoff_limit %>
38
+ <%- spec.env.each do |key, value| %>
39
+ - name: "<%= key %>"
40
+ value: "<%= value %>"
41
+ <%- end %>
42
+ restartPolicy: "<%= spec.restart_policy %>"
43
+ backoffLimit: <%= spec.backoff_limit %>
44
+ activeDeadlineSeconds: <%= spec.active_deadline_seconds %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kube_queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yuemori
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-10 00:00:00.000000000 Z
11
+ date: 2019-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: k8s-client
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -152,6 +166,7 @@ files:
152
166
  - ".travis.yml"
153
167
  - CODE_OF_CONDUCT.md
154
168
  - Dockerfile
169
+ - Dockerfile.myapp
155
170
  - Gemfile
156
171
  - LICENSE.txt
157
172
  - README.md
@@ -159,20 +174,19 @@ files:
159
174
  - bin/console
160
175
  - bin/setup
161
176
  - cloudbuild.yaml
162
- - docker/Gemfile
163
- - docker/test_worker.rb
164
177
  - exe/kube_queue
165
- - k8s/service-account.yaml
166
178
  - kube_queue.gemspec
179
+ - lib/active_job/adapters/kube_queue_adapter.rb
167
180
  - lib/kube_queue.rb
168
- - lib/kube_queue/cli.rb
169
181
  - lib/kube_queue/client.rb
170
182
  - lib/kube_queue/configuration.rb
171
183
  - lib/kube_queue/executor.rb
172
- - lib/kube_queue/job_configuration.rb
173
- - lib/kube_queue/runner.rb
184
+ - lib/kube_queue/job_specification.rb
185
+ - lib/kube_queue/manifest_builder.rb
186
+ - lib/kube_queue/railties.rb
174
187
  - lib/kube_queue/version.rb
175
188
  - lib/kube_queue/worker.rb
189
+ - lib/kube_queue/worker/dsl.rb
176
190
  - template/job.yaml
177
191
  homepage: https://github.com/yuemori/kube_queue
178
192
  licenses:
@@ -196,8 +210,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
196
210
  - !ruby/object:Gem::Version
197
211
  version: '0'
198
212
  requirements: []
199
- rubyforge_project:
200
- rubygems_version: 2.6.11
213
+ rubygems_version: 3.0.3
201
214
  signing_key:
202
215
  specification_version: 4
203
216
  summary: A background job processing with Kubernetes job for Ruby
data/docker/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem 'kube_queue', path: '/vendor/kube_queue'
@@ -1,14 +0,0 @@
1
- require 'kube_queue'
2
-
3
- class TestWorker
4
- include KubeQueue::Worker
5
-
6
- job_name 'kube-queue-test'
7
- image ENV['IMAGE_NAME']
8
- container_name 'kube-queue-test'
9
- command 'bundle', 'exec', 'kube_queue', 'TestWorker', '-r', './test_worker.rb'
10
-
11
- def perform(payload)
12
- puts payload['message']
13
- end
14
- end
@@ -1,18 +0,0 @@
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
@@ -1,56 +0,0 @@
1
- require 'logger'
2
- require 'json'
3
- require 'optparse'
4
- require 'kube_queue/runner'
5
-
6
- module KubeQueue
7
- class CLI
8
- attr_reader :argv
9
-
10
- def initialize(argv = ARGV)
11
- parse_options(argv)
12
-
13
- @argv = argv
14
- end
15
-
16
- def run
17
- load_files!
18
-
19
- runner = Runner.new(job_name)
20
-
21
- runner.run(payload)
22
- end
23
-
24
- private
25
-
26
- def job_name
27
- argv[0]
28
- end
29
-
30
- def payload
31
- payload = ENV['KUBE_QUEUE_MESSAGE_PAYLOAD']
32
-
33
- return payload if payload
34
-
35
- raise 'Payload is missing. Please set payload to KUBE_QUEUE_MESSAGE_PAYLOAD environment variable'
36
- end
37
-
38
- def load_files!
39
- require options[:require] if options[:require]
40
- end
41
-
42
- def options
43
- @options ||= {}
44
- end
45
-
46
- def parse_options(argv)
47
- parser = OptionParser.new do |o|
48
- o.on '-r', '--require [PATH|DIR]', 'Location of Rails application with workers or file to require' do |arg|
49
- options[:require] = arg
50
- end
51
- end
52
-
53
- parser.parse!(argv)
54
- end
55
- end
56
- end
@@ -1,54 +0,0 @@
1
- require 'forwardable'
2
-
3
- module KubeQueue
4
- class JobSpecification
5
- extend Forwardable
6
-
7
- attr_accessor :payload, :id, :name
8
-
9
- attr_writer :image, :job_name, :command, :container_name, :template, :backoff_limit, :restart_policy
10
-
11
- def configure
12
- yield self
13
- self
14
- end
15
-
16
- def backoff_limit
17
- @backoff_limit || 0
18
- end
19
-
20
- def restart_policy
21
- @restart_policy || 'Never'
22
- end
23
-
24
- def command
25
- @command || ['bundle', 'exec', 'kube_queue', name]
26
- end
27
-
28
- def image
29
- @image || raise
30
- end
31
-
32
- def job_name
33
- @job_name || raise
34
- end
35
-
36
- def container_name
37
- @container_name || raise
38
- end
39
-
40
- def template
41
- @template || File.read(File.expand_path('../../../template/job.yaml', __FILE__))
42
- end
43
-
44
- def to_manifest
45
- YAML.safe_load(ERB.new(template, nil, "%").result(binding))
46
- end
47
-
48
- private
49
-
50
- def binding
51
- super
52
- end
53
- end
54
- end
@@ -1,17 +0,0 @@
1
- require 'logger'
2
- require 'json'
3
-
4
- module KubeQueue
5
- class Runner
6
- def initialize(job_name)
7
- @job_name = job_name
8
- end
9
-
10
- def run(payload)
11
- payload = JSON.parse(payload)
12
-
13
- worker = KubeQueue.fetch_worker(@job_name)
14
- worker.new.perform(payload)
15
- end
16
- end
17
- end