active_job_kubernetes 0.1.0 → 0.2.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
2
  SHA256:
3
- metadata.gz: 20cb5ea1a776e1bdc7a93510580960fafea94e8dbb8fa739c849b27b772491cb
4
- data.tar.gz: 392ea4a6686dce2dc6811e4bf42a0216d1273daad44ed33cd164b1c2fb63f102
3
+ metadata.gz: bc3a0f0c67119085c1ef9ccbadc5e0638a1383639ae5023f653da136d6b7bdc9
4
+ data.tar.gz: 7207e005df5da81be6dae280f14ad1f9fb97a34294706ee5f5f2bb580e745f4f
5
5
  SHA512:
6
- metadata.gz: 8c546c75198de4773100c310bf24dfebc7f9904813380545de75b02a0b64fdd0d95b19c33e3251f5bcbc3de201bba677c5005f61bfebdbb1a8d28288d06de123
7
- data.tar.gz: 601c2c186a5316b08207f01edfe4f55716678790d9c779e278fd27078a04e759465368b94029f1fdb144f1097065f235ec415a272f63a075faa89f8fd97c7055
6
+ metadata.gz: 5fe318433e2ca0c3e41370d37777ae482a3420c3da7b50cc7641cb5b20c077c0cd6452d5f5f08d69d1d784cd672fc9ceb461b2b9d857e683ecbde256a0ea62e2
7
+ data.tar.gz: '097844e57fa772ecaf01e2303d3b393e233099604e9c405e3b3dc196ce1ac797a1ff99ba6a74d971d91c375184c298fda91c233d3b10eb375180aed78afdecaa'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ - Support Rails 7 and 8
6
+ - Require Ruby 2.7+
7
+ - Inherit from `ActiveJob::QueueAdapters::AbstractAdapter` where available
8
+ - Set `provider_job_id` for enqueued jobs
9
+ - Support delayed jobs by creating a suspended Kubernetes Job labelled
10
+ `active-job-kubernetes/suspend` and annotated with
11
+ `active-job-kubernetes/suspend-until`. `ActiveJobKubernetes.unsuspend_jobs`
12
+ must be called periodically to unsuspend jobs that are ready to run
13
+
3
14
  ## 0.1.0
4
15
 
5
16
  - Initial release
data/README.md CHANGED
@@ -20,7 +20,7 @@ bundle install
20
20
  Or install it yourself as:
21
21
 
22
22
  ```bash
23
- gem install active-job-kubernetes
23
+ gem install active_job_kubernetes
24
24
  ```
25
25
 
26
26
  ## Usage
@@ -39,7 +39,7 @@ class HelloWorldJob < ApplicationJob
39
39
  apiVersion: batch/v1
40
40
  kind: Job
41
41
  metadata:
42
- generatedName: hello-world
42
+ generateName: hello-world
43
43
  spec:
44
44
  template:
45
45
  metadata:
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'Run Rails Active Jobs as Kubernetes Jobs'
13
13
  spec.homepage = 'https://github.com/omjadas/active-job-kubernetes'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
16
16
 
17
17
  spec.metadata['homepage_uri'] = spec.homepage
18
18
  spec.metadata['source_code_uri'] = 'https://github.com/omjadas/active-job-kubernetes'
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ['lib']
29
29
 
30
+ spec.add_dependency 'activejob', '>= 7.0'
30
31
  spec.add_dependency 'kubeclient', '~> 4.0'
31
- spec.add_dependency 'rails', '>= 5.0', '<7'
32
+ spec.add_dependency 'railties', '>= 7.0'
32
33
  end
@@ -1,27 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'kubeclient'
3
+ require 'active_job'
4
+ require 'active_job_kubernetes'
4
5
 
5
6
  module ActiveJob
6
7
  module QueueAdapters
7
- class KubernetesAdapter
8
+ class KubernetesAdapter < (const_defined?(:AbstractAdapter) ? AbstractAdapter : Object)
8
9
  def enqueue(job)
9
- serialized_job = JSON.dump(job.serialize)
10
- kube_job = Kubeclient::Resource.new(job.manifest)
11
-
12
- kube_job.spec.template.spec.containers.map do |container|
13
- container.env ||= []
14
- container.env.push({
15
- 'name' => 'SERIALIZED_JOB',
16
- 'value' => serialized_job
17
- })
18
- end
19
-
20
- job.kubeclient('/apis/batch').create_job(kube_job)
10
+ ActiveJobKubernetes.create_job(job)
21
11
  end
22
12
 
23
- def enqueue_at(_job, _timestamp)
24
- raise NotImplementedError, 'Enqueueing jobs in the future is not supported.'
13
+ def enqueue_at(job, timestamp)
14
+ ActiveJobKubernetes.create_job(job, timestamp)
25
15
  end
26
16
  end
27
17
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  namespace :active_job_kubernetes do
4
4
  task run_job: :environment do
5
- ActiveJob::Base.execute(JSON.parse(ENV['SERIALIZED_JOB']))
5
+ ActiveJob::Base.execute(JSON.parse(ENV.fetch(ActiveJobKubernetes::SERIALIZED_JOB)))
6
6
  end
7
7
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveJobKubernetes
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -1,5 +1,101 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_job/queue_adapters/kubernetes_adapter'
4
- require 'active_job_kubernetes/version'
5
4
  require 'active_job_kubernetes/railtie' if defined?(Rails::Railtie)
5
+ require 'active_job_kubernetes/version'
6
+ require 'json'
7
+ require 'kubeclient'
8
+ require 'time'
9
+
10
+ module ActiveJobKubernetes
11
+ BATCH_API = '/apis/batch'
12
+ CHUNK_SIZE = 100
13
+ SERIALIZED_JOB = 'SERIALIZED_JOB'
14
+ SERVICE_ACCOUNT = '/var/run/secrets/kubernetes.io/serviceaccount'
15
+ SUSPEND = 'active-job-kubernetes/suspend'
16
+ SUSPEND_UNTIL = 'active-job-kubernetes/suspend-until'
17
+
18
+ private_constant :BATCH_API, :CHUNK_SIZE, :SERVICE_ACCOUNT
19
+
20
+ class << self
21
+ attr_writer :kubeclient, :namespace
22
+
23
+ def kubeclient
24
+ @kubeclient ||= lambda do |scope|
25
+ Kubeclient::Client.new(
26
+ "https://kubernetes.default.svc#{scope}",
27
+ 'v1',
28
+ auth_options: { bearer_token_file: "#{SERVICE_ACCOUNT}/token" },
29
+ ssl_options: { ca_file: "#{SERVICE_ACCOUNT}/ca.crt" }
30
+ )
31
+ end
32
+ end
33
+
34
+ def namespace
35
+ @namespace ||= File.read("#{SERVICE_ACCOUNT}/namespace")
36
+ end
37
+
38
+ def create_job(job, timestamp = nil)
39
+ serialized_job = JSON.dump(job.serialize)
40
+ kube_job = Kubeclient::Resource.new(job.manifest)
41
+ kube_job.metadata.namespace ||= namespace
42
+
43
+ kube_job.spec.template.spec.containers.each do |container|
44
+ container.env ||= []
45
+ container.env.push({
46
+ 'name' => SERIALIZED_JOB,
47
+ 'value' => serialized_job
48
+ })
49
+ end
50
+
51
+ if timestamp
52
+ kube_job.spec.suspend = true
53
+
54
+ kube_job.metadata.labels ||= {}
55
+ kube_job.metadata.labels[SUSPEND] = 'true'
56
+
57
+ kube_job.metadata.annotations ||= {}
58
+ kube_job.metadata.annotations[SUSPEND_UNTIL] = Time.at(timestamp).utc.iso8601
59
+ end
60
+
61
+ job.provider_job_id = kubeclient_for(job).create_job(kube_job).metadata.name
62
+ end
63
+
64
+ def unsuspend_jobs
65
+ now = Time.now
66
+ client = kubeclient.call(BATCH_API)
67
+ continue = nil
68
+
69
+ loop do
70
+ kube_jobs = client.get_jobs(
71
+ namespace: namespace,
72
+ label_selector: "#{SUSPEND}=true",
73
+ limit: CHUNK_SIZE,
74
+ continue: continue
75
+ )
76
+
77
+ kube_jobs.each do |kube_job|
78
+ suspend_until = kube_job.metadata.annotations&.[](SUSPEND_UNTIL)
79
+ next unless suspend_until && Time.iso8601(suspend_until) <= now
80
+
81
+ client.patch_job(
82
+ kube_job.metadata.name,
83
+ { metadata: { labels: { SUSPEND => 'false' } }, spec: { suspend: false } },
84
+ namespace
85
+ )
86
+ end
87
+
88
+ continue = kube_jobs.continue
89
+ break if kube_jobs.last?
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def kubeclient_for(job)
96
+ return job.kubeclient(BATCH_API) if job.respond_to?(:kubeclient)
97
+
98
+ kubeclient.call(BATCH_API)
99
+ end
100
+ end
101
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_job_kubernetes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - omjadas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-19 00:00:00.000000000 Z
11
+ date: 2026-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activejob
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: kubeclient
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -25,25 +39,19 @@ dependencies:
25
39
  - !ruby/object:Gem::Version
26
40
  version: '4.0'
27
41
  - !ruby/object:Gem::Dependency
28
- name: rails
42
+ name: railties
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
32
46
  - !ruby/object:Gem::Version
33
- version: '5.0'
34
- - - "<"
35
- - !ruby/object:Gem::Version
36
- version: '7'
47
+ version: '7.0'
37
48
  type: :runtime
38
49
  prerelease: false
39
50
  version_requirements: !ruby/object:Gem::Requirement
40
51
  requirements:
41
52
  - - ">="
42
53
  - !ruby/object:Gem::Version
43
- version: '5.0'
44
- - - "<"
45
- - !ruby/object:Gem::Version
46
- version: '7'
54
+ version: '7.0'
47
55
  description: Run Rails Active Jobs as Kubernetes Jobs
48
56
  email:
49
57
  - omja.das@gmail.com
@@ -80,7 +88,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
80
88
  requirements:
81
89
  - - ">="
82
90
  - !ruby/object:Gem::Version
83
- version: 2.5.0
91
+ version: 2.7.0
84
92
  required_rubygems_version: !ruby/object:Gem::Requirement
85
93
  requirements:
86
94
  - - ">="