active_job_k8s 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 47a17202dbfd72dab2e898ae6097df0d9f9191de31d05119e6ff4190f4038ea9
4
+ data.tar.gz: 961f6548436a79621c58b8da0a5d078e14a0db4a2517ec701a43bbed3ea5da87
5
+ SHA512:
6
+ metadata.gz: aaad5ff2c9f345e177a7b0017d91fe68e6b064392ecdf7d4c174714256d0ba2cacfeae271102845265ecb9979aafb6495859d6831694d4de5f683ead8134dbc9
7
+ data.tar.gz: 2e979a40fde5148b72fa00ebe1ab442b570b3ca9ff843a8b77f2f720ef1108423382f72a3c138caf30705f5ae58e149ca9d8543d48a65c4bfb133bdc1cbdc95c
@@ -0,0 +1,41 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ "master" ]
13
+ pull_request:
14
+ branches: [ "master" ]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ test:
21
+
22
+ runs-on: ubuntu-latest
23
+ strategy:
24
+ matrix:
25
+ ruby-version: ['2.6', '2.7']
26
+
27
+ steps:
28
+ - uses: actions/checkout@v3
29
+ - name: Set up Ruby
30
+ uses: ruby/setup-ruby@v1
31
+ with:
32
+ ruby-version: ${{ matrix.ruby-version }}
33
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
34
+ - name: Run tests
35
+ run: bundle exec rake
36
+ - name: Code Coverage Summary
37
+ uses: irongut/CodeCoverageSummary@v1.3.0
38
+ with:
39
+ filename: coverage/**/coverage.xml
40
+ fail_below_min: true
41
+ thresholds: '75 100'
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ spec/dummy
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.4
6
+ before_install: gem install bundler -v 2.1.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ # V 0.1.0
2
+ - first release as POC
3
+ - base tests
4
+ - implemented enqueue method on adapter
@@ -0,0 +1,74 @@
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 marinobonetti@gmail.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 [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in active_job_k8s.gemspec
4
+ gemspec
5
+
6
+ gem "rails","~> 6.1"
7
+ gem "rake", "~> 12.0"
8
+ gem "rspec", "~> 3.0"
9
+
10
+ gem 'simplecov', require: false
11
+ gem 'simplecov-cobertura', '~> 2.1.0', require: false
data/Gemfile.lock ADDED
@@ -0,0 +1,214 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ active_job_k8s (0.1.0)
5
+ kubeclient (~> 4.0)
6
+ rails (>= 6.0, < 8)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ actioncable (6.1.7)
12
+ actionpack (= 6.1.7)
13
+ activesupport (= 6.1.7)
14
+ nio4r (~> 2.0)
15
+ websocket-driver (>= 0.6.1)
16
+ actionmailbox (6.1.7)
17
+ actionpack (= 6.1.7)
18
+ activejob (= 6.1.7)
19
+ activerecord (= 6.1.7)
20
+ activestorage (= 6.1.7)
21
+ activesupport (= 6.1.7)
22
+ mail (>= 2.7.1)
23
+ actionmailer (6.1.7)
24
+ actionpack (= 6.1.7)
25
+ actionview (= 6.1.7)
26
+ activejob (= 6.1.7)
27
+ activesupport (= 6.1.7)
28
+ mail (~> 2.5, >= 2.5.4)
29
+ rails-dom-testing (~> 2.0)
30
+ actionpack (6.1.7)
31
+ actionview (= 6.1.7)
32
+ activesupport (= 6.1.7)
33
+ rack (~> 2.0, >= 2.0.9)
34
+ rack-test (>= 0.6.3)
35
+ rails-dom-testing (~> 2.0)
36
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
37
+ actiontext (6.1.7)
38
+ actionpack (= 6.1.7)
39
+ activerecord (= 6.1.7)
40
+ activestorage (= 6.1.7)
41
+ activesupport (= 6.1.7)
42
+ nokogiri (>= 1.8.5)
43
+ actionview (6.1.7)
44
+ activesupport (= 6.1.7)
45
+ builder (~> 3.1)
46
+ erubi (~> 1.4)
47
+ rails-dom-testing (~> 2.0)
48
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
49
+ activejob (6.1.7)
50
+ activesupport (= 6.1.7)
51
+ globalid (>= 0.3.6)
52
+ activemodel (6.1.7)
53
+ activesupport (= 6.1.7)
54
+ activerecord (6.1.7)
55
+ activemodel (= 6.1.7)
56
+ activesupport (= 6.1.7)
57
+ activestorage (6.1.7)
58
+ actionpack (= 6.1.7)
59
+ activejob (= 6.1.7)
60
+ activerecord (= 6.1.7)
61
+ activesupport (= 6.1.7)
62
+ marcel (~> 1.0)
63
+ mini_mime (>= 1.1.0)
64
+ activesupport (6.1.7)
65
+ concurrent-ruby (~> 1.0, >= 1.0.2)
66
+ i18n (>= 1.6, < 2)
67
+ minitest (>= 5.1)
68
+ tzinfo (~> 2.0)
69
+ zeitwerk (~> 2.3)
70
+ addressable (2.8.1)
71
+ public_suffix (>= 2.0.2, < 6.0)
72
+ builder (3.2.4)
73
+ concurrent-ruby (1.1.10)
74
+ crass (1.0.6)
75
+ diff-lcs (1.5.0)
76
+ docile (1.4.0)
77
+ domain_name (0.5.20190701)
78
+ unf (>= 0.0.5, < 1.0.0)
79
+ erubi (1.11.0)
80
+ ffi (1.15.5)
81
+ ffi-compiler (1.0.1)
82
+ ffi (>= 1.0.0)
83
+ rake
84
+ globalid (1.0.0)
85
+ activesupport (>= 5.0)
86
+ http (4.4.1)
87
+ addressable (~> 2.3)
88
+ http-cookie (~> 1.0)
89
+ http-form_data (~> 2.2)
90
+ http-parser (~> 1.2.0)
91
+ http-accept (1.7.0)
92
+ http-cookie (1.0.5)
93
+ domain_name (~> 0.5)
94
+ http-form_data (2.3.0)
95
+ http-parser (1.2.3)
96
+ ffi-compiler (>= 1.0, < 2.0)
97
+ i18n (1.12.0)
98
+ concurrent-ruby (~> 1.0)
99
+ jsonpath (1.1.2)
100
+ multi_json
101
+ kubeclient (4.10.1)
102
+ http (>= 3.0, < 5.0)
103
+ jsonpath (~> 1.0)
104
+ recursive-open-struct (~> 1.1, >= 1.1.1)
105
+ rest-client (~> 2.0)
106
+ loofah (2.19.0)
107
+ crass (~> 1.0.2)
108
+ nokogiri (>= 1.5.9)
109
+ mail (2.7.1)
110
+ mini_mime (>= 0.1.1)
111
+ marcel (1.0.2)
112
+ method_source (1.0.0)
113
+ mime-types (3.4.1)
114
+ mime-types-data (~> 3.2015)
115
+ mime-types-data (3.2022.0105)
116
+ mini_mime (1.1.2)
117
+ minitest (5.16.3)
118
+ multi_json (1.15.0)
119
+ netrc (0.11.0)
120
+ nio4r (2.5.8)
121
+ nokogiri (1.13.9-x86_64-linux)
122
+ racc (~> 1.4)
123
+ public_suffix (5.0.0)
124
+ racc (1.6.0)
125
+ rack (2.2.4)
126
+ rack-test (2.0.2)
127
+ rack (>= 1.3)
128
+ rails (6.1.7)
129
+ actioncable (= 6.1.7)
130
+ actionmailbox (= 6.1.7)
131
+ actionmailer (= 6.1.7)
132
+ actionpack (= 6.1.7)
133
+ actiontext (= 6.1.7)
134
+ actionview (= 6.1.7)
135
+ activejob (= 6.1.7)
136
+ activemodel (= 6.1.7)
137
+ activerecord (= 6.1.7)
138
+ activestorage (= 6.1.7)
139
+ activesupport (= 6.1.7)
140
+ bundler (>= 1.15.0)
141
+ railties (= 6.1.7)
142
+ sprockets-rails (>= 2.0.0)
143
+ rails-dom-testing (2.0.3)
144
+ activesupport (>= 4.2.0)
145
+ nokogiri (>= 1.6)
146
+ rails-html-sanitizer (1.4.3)
147
+ loofah (~> 2.3)
148
+ railties (6.1.7)
149
+ actionpack (= 6.1.7)
150
+ activesupport (= 6.1.7)
151
+ method_source
152
+ rake (>= 12.2)
153
+ thor (~> 1.0)
154
+ rake (12.3.3)
155
+ recursive-open-struct (1.1.3)
156
+ rest-client (2.1.0)
157
+ http-accept (>= 1.7.0, < 2.0)
158
+ http-cookie (>= 1.0.2, < 2.0)
159
+ mime-types (>= 1.16, < 4.0)
160
+ netrc (~> 0.8)
161
+ rexml (3.2.5)
162
+ rspec (3.11.0)
163
+ rspec-core (~> 3.11.0)
164
+ rspec-expectations (~> 3.11.0)
165
+ rspec-mocks (~> 3.11.0)
166
+ rspec-core (3.11.0)
167
+ rspec-support (~> 3.11.0)
168
+ rspec-expectations (3.11.1)
169
+ diff-lcs (>= 1.2.0, < 2.0)
170
+ rspec-support (~> 3.11.0)
171
+ rspec-mocks (3.11.1)
172
+ diff-lcs (>= 1.2.0, < 2.0)
173
+ rspec-support (~> 3.11.0)
174
+ rspec-support (3.11.1)
175
+ simplecov (0.21.2)
176
+ docile (~> 1.1)
177
+ simplecov-html (~> 0.11)
178
+ simplecov_json_formatter (~> 0.1)
179
+ simplecov-cobertura (2.1.0)
180
+ rexml
181
+ simplecov (~> 0.19)
182
+ simplecov-html (0.12.3)
183
+ simplecov_json_formatter (0.1.4)
184
+ sprockets (4.1.1)
185
+ concurrent-ruby (~> 1.0)
186
+ rack (> 1, < 3)
187
+ sprockets-rails (3.4.2)
188
+ actionpack (>= 5.2)
189
+ activesupport (>= 5.2)
190
+ sprockets (>= 3.0.0)
191
+ thor (1.2.1)
192
+ tzinfo (2.0.5)
193
+ concurrent-ruby (~> 1.0)
194
+ unf (0.1.4)
195
+ unf_ext
196
+ unf_ext (0.0.8.2)
197
+ websocket-driver (0.7.5)
198
+ websocket-extensions (>= 0.1.0)
199
+ websocket-extensions (0.1.5)
200
+ zeitwerk (2.6.1)
201
+
202
+ PLATFORMS
203
+ x86_64-linux
204
+
205
+ DEPENDENCIES
206
+ active_job_k8s!
207
+ rails (~> 6.1)
208
+ rake (~> 12.0)
209
+ rspec (~> 3.0)
210
+ simplecov
211
+ simplecov-cobertura (~> 2.1.0)
212
+
213
+ BUNDLED WITH
214
+ 2.3.7
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Marino Bonetti
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.
data/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # ActiveJobK8s
2
+
3
+ WIP gem to make active job work with kubernetes jobs.
4
+
5
+ Roadmap for V1.0:
6
+ - [x] ActiveJob.perform_later create a Job in k8s that will execute the job
7
+ - [ ] ActiveJob.perform_later with delay create a Job in k8s in suspended mode,
8
+ a task will enable it as soon as the time is reached
9
+ - [ ] Limiting the number of concurrent jobs (if there are more jobs they will be created in suspended mode)
10
+
11
+ Future:
12
+ - [ ] Metrics (because everyone like metrics)
13
+ - [ ] Retry options
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'active_job_k8s'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle install
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install active_job_k8s
30
+
31
+ ## Usage
32
+
33
+ ### Adapter Configuration
34
+ Configure the KubeClient as documented here: https://github.com/ManageIQ/kubeclient and instantiate the adapter.
35
+
36
+ ES Local with default kind cluster:
37
+ ```ruby
38
+
39
+ kubeclient_config = Kubeclient::Config.read(ENV['KUBECONFIG'] || File.join(Dir.home, '/.kube/config'))
40
+
41
+ config.active_job.queue_adapter = ActiveJob::QueueAdapters::K8sAdapter.new(
42
+ kubeclient_context: kubeclient_config.context('kind-kind')
43
+ )
44
+
45
+ ```
46
+ ES production configuration:
47
+ ```ruby
48
+ Rails.application.configure do
49
+
50
+ auth_options = {
51
+ bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
52
+ }
53
+ ssl_options = {}
54
+ if File.exist?("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt")
55
+ ssl_options[:ca_file] = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
56
+ end
57
+ api_endpoint = "https://kubernetes.default.svc"
58
+ api_version = "v1"
59
+ namespace = File.read("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
60
+
61
+ context = Kubeclient::Config::Context.new(
62
+ api_endpoint,
63
+ api_version,
64
+ ssl_options,
65
+ auth_options,
66
+ namespace
67
+ )
68
+
69
+ config.active_job.queue_adapter = ActiveJob::QueueAdapters::K8sAdapter.new(
70
+ kubeclient_context: context
71
+ )
72
+
73
+ end
74
+ ```
75
+
76
+ ### Kubernetes Configuration
77
+
78
+ The adapter should be capable to query the kubernetes cluster, this is the role to create inside the namespace, it will
79
+ add api capacity o jobs to the default ServiceAccount
80
+
81
+ ```yaml
82
+ kind: Role
83
+ apiVersion: rbac.authorization.k8s.io/v1
84
+ metadata:
85
+ name: jobs-edit
86
+ namespace: YOURN-NAMESPACE
87
+ rules:
88
+ - apiGroups:
89
+ - "batch"
90
+ resources:
91
+ - jobs
92
+ verbs:
93
+ - get
94
+ - list
95
+ - watch
96
+ - create
97
+ - delete
98
+ ---
99
+ kind: RoleBinding
100
+ apiVersion: rbac.authorization.k8s.io/v1
101
+ metadata:
102
+ name: jobs-edit
103
+ namespace: YOURN-NAMESPACE
104
+ subjects:
105
+ - kind: ServiceAccount
106
+ name: default
107
+ namespace: default
108
+ roleRef:
109
+ apiGroup: rbac.authorization.k8s.io
110
+ kind: Role
111
+ name: jobs-edit
112
+ ```
113
+
114
+ ### Job configuration
115
+
116
+ Inside you job you should describe the initial manifest for the [KubernetesJob](https://kubernetes.io/docs/concepts/workloads/controllers/job/)
117
+
118
+ ES:
119
+ ```ruby
120
+ class HelloWorldJob < ApplicationJob
121
+ queue_as :default
122
+
123
+ def perform(*args)
124
+ Rails.logger.debug "Hello World"
125
+ end
126
+
127
+ def manifest
128
+ YAML.safe_load(
129
+ <<~MANIFEST
130
+ apiVersion: batch/v1
131
+ kind: Job
132
+ metadata:
133
+ name: scheduled-job-name
134
+ namespace: default
135
+ spec:
136
+ template:
137
+ spec:
138
+ restartPolicy: Never
139
+ containers:
140
+ - name: app-job
141
+ image: image_of_the_rails_application
142
+ imagePullPolicy: IfNotPresent
143
+
144
+ MANIFEST
145
+ )
146
+ end
147
+ end
148
+ ```
149
+
150
+ The command will be inserted by the gem if not present.
151
+ ```yaml
152
+ command: ["rails"]
153
+ args: ["active_job_k8s:run_job"]
154
+ ```
155
+ To the name of the Job wi will append a timestamp to make it uniq
156
+
157
+ ## Development
158
+
159
+ Requirements: [kind](https://kind.sigs.k8s.io/)
160
+
161
+ After checking out the repo, run `bin/setup` to install the environment (cluster and dummy app).
162
+
163
+ ## Contributing
164
+
165
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_job_k8s. This project is
166
+ intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to
167
+ the [code of conduct](https://github.com/oniram88/active_job_k8s/blob/master/CODE_OF_CONDUCT.md).
168
+
169
+ ## License
170
+
171
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
172
+
173
+ ## Code of Conduct
174
+
175
+ Everyone interacting in the ActiveJobK8s project's codebases, issue trackers, chat rooms and mailing lists is expected
176
+ to follow the [code of conduct](https://github.com/[USERNAME]/active_job_k8s/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,32 @@
1
+ require_relative 'lib/active_job_k8s/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "active_job_k8s"
5
+ spec.version = ActiveJobK8s::VERSION
6
+ spec.authors = ["Marino Bonetti"]
7
+ spec.email = ["marinobonetti@gmail.com"]
8
+
9
+ spec.summary = "ActiveJob adapter for kubernetes job"
10
+ spec.homepage = "https://github.com/oniram88/active_job_k8s"
11
+ spec.license = "MIT"
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
+
14
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.add_dependency 'kubeclient', '~> 4.0' #https://github.com/ManageIQ/kubeclient
30
+ spec.add_dependency 'rails', '>= 6.0', '<8'
31
+
32
+ end
data/bin/cluster.yaml ADDED
@@ -0,0 +1,11 @@
1
+ kind: Cluster
2
+ apiVersion: kind.x-k8s.io/v1alpha4
3
+ name: active-job-cluster
4
+ nodes:
5
+ - role: control-plane
6
+ extraMounts:
7
+ - hostPath: ./
8
+ containerPath: /application
9
+ extraPortMappings:
10
+ - containerPort: 30335
11
+ hostPort: 3000
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "active_job_k8s"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Prepariamo l'applicazione esempio
9
+ rm -fr spec/dummy
10
+ bundle exec rails new spec/dummy --skip-git --skip-javascript --skip-hotwire --skip-jbuilder --skip-test --skip-bootsnap --force
11
+
12
+ echo "Changes to application"
13
+
14
+ sed -i 's/"2.7.4"/">= 2.7.4"/' spec/dummy/Gemfile
15
+ sed -i '/end/i root "main#index"' spec/dummy/config/routes.rb
16
+ sed -i '/root "main#index"/i get "create_job", to: "main#create_job", as: "create_job"' spec/dummy/config/routes.rb
17
+
18
+ echo '
19
+ class MainController < ApplicationController
20
+ include ActionView::Helpers::UrlHelper
21
+ def index
22
+ render inline: "#{link_to HelloWorldJob.name,create_job_path}"
23
+ end
24
+
25
+ def create_job
26
+ HelloWorldJob.perform_later
27
+ redirect_to root_path
28
+ end
29
+
30
+ end
31
+ ' > spec/dummy/app/controllers/main_controller.rb
32
+
33
+ echo '
34
+ Rails.application.configure do
35
+
36
+ auth_options = {
37
+ bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
38
+ }
39
+ ssl_options = {}
40
+ if File.exist?("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt")
41
+ ssl_options[:ca_file] = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
42
+ end
43
+ api_endpoint = "https://kubernetes.default.svc"
44
+ api_version = "v1"
45
+ namespace = File.read("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
46
+
47
+ context = Kubeclient::Config::Context.new(
48
+ api_endpoint,
49
+ api_version,
50
+ ssl_options,
51
+ auth_options,
52
+ namespace
53
+ )
54
+
55
+ config.active_job.queue_adapter = ActiveJob::QueueAdapters::K8sAdapter.new(
56
+ kubeclient_context: context
57
+ )
58
+
59
+ end
60
+ ' > spec/dummy/config/initializers/active_job_k8s.rb
61
+
62
+ echo '
63
+ class HelloWorldJob < ApplicationJob
64
+ queue_as :default
65
+
66
+ def perform(*args)
67
+ Rails.logger.debug "Hello World"
68
+ FileUtils.touch(Rails.root.join("tmp","#{SecureRandom.hex}.test"))
69
+ end
70
+
71
+ def manifest
72
+ YAML.safe_load(
73
+ <<~MANIFEST
74
+ apiVersion: batch/v1
75
+ kind: Job
76
+ metadata:
77
+ name: scheduled-job-name
78
+ namespace: default
79
+ spec:
80
+ template:
81
+ spec:
82
+ restartPolicy: Never
83
+ containers:
84
+ - name: app-job
85
+ image: activejobk8s:0.1.0
86
+ imagePullPolicy: IfNotPresent
87
+ env:
88
+ - name: RAILS_SERVE_STATIC_FILES
89
+ value: "1"
90
+ - name: RAILS_LOG_TO_STDOUT
91
+ value: "1"
92
+ - name: RAILS_ENV
93
+ value: "development"
94
+ command: ["/bin/sh"]
95
+ args:
96
+ - "-ec"
97
+ - "bundle install && bundle exec rails active_job_k8s:run_job"
98
+ volumeMounts:
99
+ - mountPath: /usr/src/app
100
+ name: application-volume
101
+ volumes:
102
+ - name: application-volume
103
+ hostPath:
104
+ # directory location on host
105
+ path: /application
106
+
107
+ MANIFEST
108
+ )
109
+ end
110
+ end
111
+ ' > spec/dummy/app/jobs/hello_world_job.rb
112
+
113
+ echo 'FROM ruby:3.0.4-alpine3.16
114
+
115
+ RUN apk update \
116
+ && apk upgrade \
117
+ && apk add --update alpine-conf bash tzdata build-base curl-dev curl git yaml-dev zlib-dev
118
+
119
+ RUN gem install bundler:2.3.7
120
+
121
+ # throw errors if Gemfile has been modified since Gemfile.lock
122
+ WORKDIR /usr/src/app/spec/dummy
123
+
124
+ COPY Gemfile Gemfile.lock ./
125
+ RUN bundle install
126
+
127
+ COPY . .
128
+
129
+ CMD ["/bin/sh", "-ec", "bundle install && bundle exec rails s -b 0.0.0.0 -p 3000"]' > spec/dummy/Dockerfile
130
+
131
+ docker build -t activejobk8s:0.1.0 spec/dummy/.
132
+
133
+ # aggiungiamo la path ad active_job_k8s nel Gemfile che viene poi usato nel cluster e che punta alla gemma.
134
+ echo "gem 'active_job_k8s', path:'/usr/src/app' " >> spec/dummy/Gemfile
135
+
136
+
137
+ echo "START KIND ENV"
138
+ kind delete cluster -n active-job-cluster
139
+ kind create cluster --config bin/cluster.yaml
140
+ kubectl config set-context active-job-cluster
141
+
142
+ echo "SLEEP for cluster to be ready"
143
+ sleep 10
144
+
145
+ kind load docker-image activejobk8s:0.1.0 -n active-job-cluster
146
+
147
+ kubectl apply -f - <<EOF
148
+ apiVersion: v1
149
+ kind: Pod
150
+ metadata:
151
+ name: test-activejobk8s
152
+ namespace: default
153
+ labels:
154
+ app: active-job-app
155
+ spec:
156
+ containers:
157
+ - name: app
158
+ image: activejobk8s:0.1.0
159
+ imagePullPolicy: Never
160
+ ports:
161
+ - name: app
162
+ containerPort: 3000
163
+ protocol: TCP
164
+ env:
165
+ - name: RAILS_SERVE_STATIC_FILES
166
+ value: '1'
167
+ - name: RAILS_LOG_TO_STDOUT
168
+ value: '1'
169
+ - name: RAILS_ENV
170
+ value: 'development'
171
+ volumeMounts:
172
+ - mountPath: /usr/src/app
173
+ name: application-volume
174
+ volumes:
175
+ - name: application-volume
176
+ hostPath:
177
+ # directory location on host
178
+ path: /application
179
+ ---
180
+ kind: Role
181
+ apiVersion: rbac.authorization.k8s.io/v1
182
+ metadata:
183
+ name: jobs-edit
184
+ namespace: default
185
+ rules:
186
+ - apiGroups:
187
+ - "batch"
188
+ resources:
189
+ - jobs
190
+ verbs:
191
+ - get
192
+ - list
193
+ - watch
194
+ - create
195
+ - delete
196
+ ---
197
+ kind: RoleBinding
198
+ apiVersion: rbac.authorization.k8s.io/v1
199
+ metadata:
200
+ name: jobs-edit
201
+ namespace: default
202
+ subjects:
203
+ - kind: ServiceAccount
204
+ name: default
205
+ namespace: default
206
+ roleRef:
207
+ apiGroup: rbac.authorization.k8s.io
208
+ kind: Role
209
+ name: jobs-edit
210
+ ---
211
+ apiVersion: v1
212
+ kind: Service
213
+ metadata:
214
+ name: app-service
215
+ namespace: default
216
+ labels:
217
+ app: app-service
218
+ spec:
219
+ ports:
220
+ - name: 3000-3000
221
+ protocol: TCP
222
+ port: 3000
223
+ targetPort: 3000
224
+ nodePort: 30335
225
+ selector:
226
+ app: active-job-app
227
+ type: NodePort
228
+ EOF
229
+
230
+ echo "Open browser to 0.0.0.0:3000"
231
+ open http://0.0.0.0:3000
@@ -0,0 +1,27 @@
1
+ require 'kubeclient'
2
+ require 'active_job'
3
+ require 'active_job_k8s/scheduler'
4
+
5
+ module ActiveJob
6
+ module QueueAdapters
7
+ class K8sAdapter
8
+
9
+ attr_reader :scheduler
10
+
11
+ def initialize(**executor_options)
12
+ @scheduler = ActiveJobK8s::Scheduler.new(**executor_options)
13
+ end
14
+
15
+ def enqueue(job)
16
+ scheduler.create_job(job)
17
+ end
18
+
19
+ def enqueue_at(*)
20
+ # :nodoc:
21
+ raise NotImplementedError, "It will be implemented in the future. Read the roadmap"
22
+ end
23
+
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJobK8s
4
+ class Railtie < Rails::Railtie
5
+ rake_tasks do
6
+ require 'active_job_k8s/tasks'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,60 @@
1
+ require "kubeclient"
2
+ require "active_job"
3
+ require "json"
4
+
5
+ module ActiveJobK8s
6
+ class Scheduler
7
+
8
+ attr_reader :kubeclient_context
9
+
10
+ # @param [Hash{ kubeclient_context: [Kubeclient::Config::Context] }] opts
11
+ def initialize(**opts)
12
+ raise "No KubeClientContext given" if opts[:kubeclient_context].nil?
13
+ # or to use a specific context, by name:
14
+ @kubeclient_context = opts[:kubeclient_context]
15
+
16
+ end
17
+
18
+ def create_job(job)
19
+
20
+ serialized_job = JSON.dump(job.serialize)
21
+
22
+ kube_job = Kubeclient::Resource.new(job.manifest)
23
+
24
+ # kube_job.spec.suspend = false FIXME complete for delayed jobs
25
+ kube_job.metadata.name = "#{kube_job.metadata.name}-#{job.job_id}"
26
+ kube_job.metadata.job_id = job.job_id
27
+ kube_job.metadata.queue_name = job.queue_name
28
+ kube_job.metadata.namespace = kube_job.metadata.namespace || kubeclient_context.namespace
29
+ kube_job.spec.template.spec.containers.map do |container|
30
+ container.env ||= []
31
+ container.env.push({
32
+ 'name' => 'SERIALIZED_JOB',
33
+ 'value' => serialized_job
34
+ })
35
+
36
+ if container.command.blank?
37
+ container.command = ["rails"]
38
+ container.args = ["active_job_k8s:run_job"]
39
+ end
40
+ end
41
+ kube_job.spec.ttlSecondsAfterFinished = 300 #number of seconds the job will be erased
42
+
43
+ client.create_job(kube_job)
44
+ end
45
+
46
+ def self.execute_job
47
+ ActiveJob::Base.execute(JSON.parse(ENV['SERIALIZED_JOB']))
48
+ end
49
+
50
+ protected
51
+
52
+ def client
53
+ @client ||= Kubeclient::Client.new(@kubeclient_context.api_endpoint + '/apis/batch',
54
+ @kubeclient_context.api_version || 'v1',
55
+ ssl_options: @kubeclient_context.ssl_options,
56
+ auth_options: @kubeclient_context.auth_options)
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :active_job_k8s do
4
+
5
+ desc "Execute the job from ENV: SERIALIZED_JOB"
6
+ task run_job: :environment do
7
+ ActiveJobK8s::Scheduler.execute_job
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveJobK8s
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,8 @@
1
+ require "active_job_k8s/version"
2
+ require "active_job/queue_adapters/k8s_adapter"
3
+ require 'active_job_k8s/railtie' if defined?(Rails::Railtie)
4
+
5
+ module ActiveJobK8s
6
+ class Error < StandardError; end
7
+ # Your code goes here...
8
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_job_k8s
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Marino Bonetti
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-10-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: kubeclient
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '8'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '6.0'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '8'
47
+ description:
48
+ email:
49
+ - marinobonetti@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - ".github/workflows/ruby.yml"
55
+ - ".gitignore"
56
+ - ".rspec"
57
+ - ".travis.yml"
58
+ - CHANGELOG.md
59
+ - CODE_OF_CONDUCT.md
60
+ - Gemfile
61
+ - Gemfile.lock
62
+ - LICENSE.txt
63
+ - README.md
64
+ - Rakefile
65
+ - active_job_k8s.gemspec
66
+ - bin/cluster.yaml
67
+ - bin/console
68
+ - bin/setup
69
+ - lib/active_job/queue_adapters/k8s_adapter.rb
70
+ - lib/active_job_k8s.rb
71
+ - lib/active_job_k8s/railtie.rb
72
+ - lib/active_job_k8s/scheduler.rb
73
+ - lib/active_job_k8s/tasks.rb
74
+ - lib/active_job_k8s/version.rb
75
+ homepage: https://github.com/oniram88/active_job_k8s
76
+ licenses:
77
+ - MIT
78
+ metadata:
79
+ allowed_push_host: https://rubygems.org
80
+ homepage_uri: https://github.com/oniram88/active_job_k8s
81
+ source_code_uri: https://github.com/oniram88/active_job_k8s
82
+ changelog_uri: https://github.com/oniram88/active_job_k8s/CHANGELOG.md
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 2.3.0
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubygems_version: 3.2.33
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: ActiveJob adapter for kubernetes job
102
+ test_files: []