From e0b07ddba2de1f0c6439a0d4cf9f8f5064f1e3fa Mon Sep 17 00:00:00 2001 From: Mike Eirih Date: Tue, 1 Apr 2025 11:41:33 +0700 Subject: [PATCH 1/2] Allow passing cronjob options --- .gitignore | 18 +++++++++-- .rubocop.yml | 3 +- .ruby-version | 2 +- Appraisals | 2 +- Gemfile | 13 ++++++++ cron-kubernetes.gemspec | 14 ++------ gemfiles/kubeclient_4.gemfile | 2 +- lib/cron_kubernetes.rb | 4 +-- lib/cron_kubernetes/context/kubectl.rb | 4 +-- lib/cron_kubernetes/cron_tab.rb | 4 +-- lib/cron_kubernetes/scheduler.rb | 23 ++++++------- spec/cron_kubernetes/cron_job_spec.rb | 1 + .../kubeclient_context_spec.rb | 32 +++++++++++-------- spec/cron_kubernetes/scheduler_spec.rb | 4 +-- spec/spec_helper.rb | 2 +- 15 files changed, 76 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index 2b620a2..3abcdb9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,16 @@ -**/.bundle/ +# Ignore bundler config. +/.bundle + +# Ignore all environment files (except templates). +/.env* +!/.env*.erb + +# Ignore editor config. +/.idea +/.vscode +/.zed + +# Ignore all log files and temp files. /.yardoc /_yardoc/ /coverage/ @@ -6,8 +18,10 @@ /pkg/ /spec/reports/ /tmp/ + +# Ignore all cache files. /Gemfile.lock **/*.gemfile.lock -# rspec failure tracking +# Ignore rspec status files. .rspec_status diff --git a/.rubocop.yml b/.rubocop.yml index 7617916..495ea77 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,7 +2,8 @@ AllCops: Exclude: - "gemfiles/*" SuggestExtensions: false - TargetRubyVersion: 3.2 + TargetRubyVersion: 3.4 + NewCops: enable Documentation: Exclude: diff --git a/.ruby-version b/.ruby-version index be94e6f..4d9d11c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.2.2 +3.4.2 diff --git a/Appraisals b/Appraisals index a2d0f92..9832a11 100644 --- a/Appraisals +++ b/Appraisals @@ -5,5 +5,5 @@ appraise "kubeclient-3" do end appraise "kubeclient-4" do - gem "kubeclient", "4.0.0" + gem "kubeclient", "4.12.0" end diff --git a/Gemfile b/Gemfile index 5b7ecd6..1ed9940 100644 --- a/Gemfile +++ b/Gemfile @@ -5,3 +5,16 @@ source "https://rubygems.org" git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } gemspec + +group :development, :test do + gem "appraisal" + gem "bundler", "~> 2.6" + gem "bundler-audit", "~> 0" + gem "mocha", "~> 2.7" + gem "rake", "~> 13.2" + gem "rspec", "~> 3.13" + gem "rubocop", "~> 1.75" + + # For connecting to a GKE cluster in development/test + gem "googleauth" +end diff --git a/cron-kubernetes.gemspec b/cron-kubernetes.gemspec index 8cf2cc3..2ef2e80 100644 --- a/cron-kubernetes.gemspec +++ b/cron-kubernetes.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |spec| spec.description = "Configure and deploy Kubernetes CronJobs from ruby with a single schedule." spec.homepage = "https://github.com/keylimetoolbox/cron-kubernetes" spec.license = "MIT" - spec.required_ruby_version = ">= 3.2" + spec.required_ruby_version = "~> 3.4" spec.files = `git ls-files -z`.split("\x0").reject do |f| f.match(%r{^(test|spec|features)/}) @@ -25,15 +25,5 @@ Gem::Specification.new do |spec| spec.executables << "cron_kubernetes" spec.add_dependency "kubeclient", ">= 3.1.2", "< 5.0" - - spec.add_development_dependency "appraisal" - spec.add_development_dependency "bundler", "~> 2.4" - spec.add_development_dependency "bundler-audit", "~> 0" - spec.add_development_dependency "mocha", "~> 1.3" - spec.add_development_dependency "rake", "~> 13.1" - spec.add_development_dependency "rspec", "~> 3.12" - spec.add_development_dependency "rubocop", "~> 1.57" - - # For connecting to a GKE cluster in development/test - spec.add_development_dependency "googleauth" + spec.metadata["rubygems_mfa_required"] = "true" end diff --git a/gemfiles/kubeclient_4.gemfile b/gemfiles/kubeclient_4.gemfile index 343489e..fb3d989 100644 --- a/gemfiles/kubeclient_4.gemfile +++ b/gemfiles/kubeclient_4.gemfile @@ -2,6 +2,6 @@ source "https://rubygems.org" -gem "kubeclient", "4.0.0" +gem "kubeclient", "4.12.0" gemspec path: "../" diff --git a/lib/cron_kubernetes.rb b/lib/cron_kubernetes.rb index 29cb79b..b2d1eed 100644 --- a/lib/cron_kubernetes.rb +++ b/lib/cron_kubernetes.rb @@ -32,8 +32,8 @@ module CronKubernetes define_setting :kubeclient, nil class << self - def schedule(&block) - CronKubernetes::Scheduler.instance.instance_eval(&block) + def schedule(&) + CronKubernetes::Scheduler.instance.instance_eval(&) end end end diff --git a/lib/cron_kubernetes/context/kubectl.rb b/lib/cron_kubernetes/context/kubectl.rb index fd2681a..655cc5c 100644 --- a/lib/cron_kubernetes/context/kubectl.rb +++ b/lib/cron_kubernetes/context/kubectl.rb @@ -23,7 +23,7 @@ def context private def kubeconfig - File.join(ENV["HOME"], ".kube", "config") + File.join(Dir.home, ".kube", "config") end def auth_options(config) @@ -34,7 +34,7 @@ def auth_options(config) end def google_application_default_credentials - return unless defined?(Google) && defined?(Google::Auth) + return unless defined?(Google::Auth) {bearer_token: Kubeclient::GoogleApplicationDefaultCredentials.token} end diff --git a/lib/cron_kubernetes/cron_tab.rb b/lib/cron_kubernetes/cron_tab.rb index d7819bc..12e6714 100644 --- a/lib/cron_kubernetes/cron_tab.rb +++ b/lib/cron_kubernetes/cron_tab.rb @@ -60,11 +60,11 @@ def update_cron_job(job) end def index_cron_jobs(jobs) - jobs.map { |job| ["#{job.identifier}-#{job.name}", job] }.to_h + jobs.to_h { |job| ["#{job.identifier}-#{job.name}", job] } end def index_kubernetes_cron_jobs(jobs) - jobs.map { |job| [job.metadata.name, job] }.to_h + jobs.to_h { |job| [job.metadata.name, job] } end end end diff --git a/lib/cron_kubernetes/scheduler.rb b/lib/cron_kubernetes/scheduler.rb index bd1e811..8d8e568 100644 --- a/lib/cron_kubernetes/scheduler.rb +++ b/lib/cron_kubernetes/scheduler.rb @@ -13,21 +13,21 @@ def initialize @identifier = CronKubernetes.identifier end - def rake(task, schedule:, name: nil) + def rake(task, schedule:, name: nil, cron_job_settings: {}) rake_command = "bundle exec rake #{task} --silent" rake_command = "RAILS_ENV=#{rails_env} #{rake_command}" if rails_env - @schedule << new_cron_job(schedule, rake_command, name) + @schedule << new_cron_job(schedule, rake_command, name, cron_job_settings) end - def runner(ruby_command, schedule:, name: nil) + def runner(ruby_command, schedule:, name: nil, cron_job_settings: {}) env = nil env = "-e #{rails_env} " if rails_env runner_command = "bin/rails runner #{env}'#{ruby_command}'" - @schedule << new_cron_job(schedule, runner_command, name) + @schedule << new_cron_job(schedule, runner_command, name, cron_job_settings) end - def command(command, schedule:, name: nil) - @schedule << new_cron_job(schedule, command, name) + def command(command, schedule:, name: nil, cron_job_settings: {}) + @schedule << new_cron_job(schedule, command, name, cron_job_settings) end private @@ -42,18 +42,19 @@ def make_command(command) end end - def new_cron_job(schedule, command, name) + def new_cron_job(schedule, command, name, cron_job_settings) CronJob.new( schedule:, - command: make_command(command), - job_manifest: CronKubernetes.manifest, + command: make_command(command), + job_manifest: CronKubernetes.manifest, + cron_job_settings:, name:, - identifier: @identifier + identifier: @identifier ) end def rails_env - ENV["RAILS_ENV"] + ENV.fetch("RAILS_ENV", nil) end def root diff --git a/spec/cron_kubernetes/cron_job_spec.rb b/spec/cron_kubernetes/cron_job_spec.rb index c04a2f2..0db12b3 100644 --- a/spec/cron_kubernetes/cron_job_spec.rb +++ b/spec/cron_kubernetes/cron_job_spec.rb @@ -88,6 +88,7 @@ cron-kubernetes-identifier: my-app spec: schedule: "*/1 * * * *" + concurrencyPolicy: Forbid jobTemplate: metadata: spec: diff --git a/spec/cron_kubernetes/kubeclient_context_spec.rb b/spec/cron_kubernetes/kubeclient_context_spec.rb index 62992a7..070c2c5 100644 --- a/spec/cron_kubernetes/kubeclient_context_spec.rb +++ b/spec/cron_kubernetes/kubeclient_context_spec.rb @@ -3,6 +3,10 @@ require "spec_helper" require "googleauth" +# Define Struct classes to replace OpenStruct +KubeConfigContext = Struct.new(:api_endpoint, :api_version, :namespace, :auth_options, :ssl_options) +KubeConfig = Struct.new(:context) + RSpec.describe CronKubernetes::KubeclientContext do let(:context) { CronKubernetes::KubeclientContext.context } @@ -57,13 +61,13 @@ context "without Google default credentials" do let(:config) do - OpenStruct.new( - context: OpenStruct.new( - api_endpoint: "https://127.0.0.1:8443", - api_version: "v1", - namespace: nil, - auth_options: {bearer_token: "token"}, - ssl_options: {ca_file: "/path/to/ca.crt"} + KubeConfig.new( + KubeConfigContext.new( + "https://127.0.0.1:8443", + "v1", + nil, + {bearer_token: "token"}, + {ca_file: "/path/to/ca.crt"} ) ) end @@ -81,13 +85,13 @@ context "with Google default credentials" do let(:config) do - OpenStruct.new( - context: OpenStruct.new( - api_endpoint: "https://127.0.0.1:8443", - api_version: "v1", - namespace: nil, - auth_options: {}, - ssl_options: {} + KubeConfig.new( + KubeConfigContext.new( + "https://127.0.0.1:8443", + "v1", + nil, + {}, + {} ) ) end diff --git a/spec/cron_kubernetes/scheduler_spec.rb b/spec/cron_kubernetes/scheduler_spec.rb index d5c829e..9f96a0b 100644 --- a/spec/cron_kubernetes/scheduler_spec.rb +++ b/spec/cron_kubernetes/scheduler_spec.rb @@ -62,7 +62,7 @@ context "when RAILS_ENV is defined" do before do - @rails_env = ENV["RAILS_ENV"] + @rails_env = ENV.fetch("RAILS_ENV", nil) ENV["RAILS_ENV"] = "production" end @@ -103,7 +103,7 @@ context "when RAILS_ENV is defined" do before do - @rails_env = ENV["RAILS_ENV"] + @rails_env = ENV.fetch("RAILS_ENV", nil) ENV["RAILS_ENV"] = "production" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1bd8af9..192e0bc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,7 +5,7 @@ # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. -Dir[File.expand_path("support/**/*.rb", __dir__)].sort.each { |f| require f } +Dir[File.expand_path("support/**/*.rb", __dir__)].each { |f| require f } RSpec.configure do |config| # Enable flags like --only-failures and --next-failure From 566c93a1bcd4123914a6f6dc5b2bb660e6f0fa34 Mon Sep 17 00:00:00 2001 From: Mike Eirih Date: Tue, 1 Apr 2025 11:42:02 +0700 Subject: [PATCH 2/2] Rubocop EnforcedHashRocketStyle -> key --- .rubocop.yml | 4 +-- lib/cron_kubernetes/configurable.rb | 4 +-- lib/cron_kubernetes/context/kubectl.rb | 2 +- lib/cron_kubernetes/cron_job.rb | 48 +++++++++++++++----------- lib/cron_kubernetes/scheduler.rb | 6 ++-- spec/cron_kubernetes/cron_job_spec.rb | 22 ++++++------ spec/cron_kubernetes/cron_tab_spec.rb | 24 ++++++------- 7 files changed, 58 insertions(+), 52 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 495ea77..9bf35f9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,8 +15,8 @@ Style/StringLiterals: Layout/LineLength: Max: 120 Layout/HashAlignment: - EnforcedHashRocketStyle: table - EnforcedColonStyle: table + EnforcedHashRocketStyle: key + EnforcedColonStyle: key Layout/SpaceInsideHashLiteralBraces: EnforcedStyle: no_space Style/RaiseArgs: diff --git a/lib/cron_kubernetes/configurable.rb b/lib/cron_kubernetes/configurable.rb index e257e51..1ef110f 100644 --- a/lib/cron_kubernetes/configurable.rb +++ b/lib/cron_kubernetes/configurable.rb @@ -27,9 +27,9 @@ def define_setting(name, default = nil) private - def define_class_method(name, &block) + def define_class_method(name, &) (class << self; self; end).instance_eval do - define_method(name, &block) + define_method(name, &) end end end diff --git a/lib/cron_kubernetes/context/kubectl.rb b/lib/cron_kubernetes/context/kubectl.rb index 655cc5c..e873d07 100644 --- a/lib/cron_kubernetes/context/kubectl.rb +++ b/lib/cron_kubernetes/context/kubectl.rb @@ -16,7 +16,7 @@ def context config.context.api_version, config.context.namespace, auth_options: auth_options(config), - ssl_options: config.context.ssl_options + ssl_options: config.context.ssl_options ) end diff --git a/lib/cron_kubernetes/cron_job.rb b/lib/cron_kubernetes/cron_job.rb index e7a8b7f..0a1246e 100644 --- a/lib/cron_kubernetes/cron_job.rb +++ b/lib/cron_kubernetes/cron_job.rb @@ -5,36 +5,29 @@ module CronKubernetes # A single job to run on a given schedule. class CronJob - attr_accessor :schedule, :command, :job_manifest, :name, :identifier - - def initialize(schedule: nil, command: nil, job_manifest: nil, name: nil, identifier: nil) - @schedule = schedule - @command = command - @job_manifest = job_manifest - @name = name - @identifier = identifier + attr_accessor :schedule, :command, :job_manifest, :name, :identifier, :cron_job_settings + + def initialize(options = {}) + @schedule = options[:schedule] + @command = options[:command] + @job_manifest = options[:job_manifest] + @name = options[:name] + @identifier = options[:identifier] + @cron_job_settings = options[:cron_job_settings] || {} end - # rubocop:disable Metrics/MethodLength def cron_job_manifest { "apiVersion" => "batch/v1", - "kind" => "CronJob", - "metadata" => { - "name" => "#{identifier}-#{cron_job_name}", + "kind" => "CronJob", + "metadata" => { + "name" => "#{identifier}-#{cron_job_name}", "namespace" => namespace, - "labels" => {"cron-kubernetes-identifier" => identifier} + "labels" => {"cron-kubernetes-identifier" => identifier} }, - "spec" => { - "schedule" => schedule, - "jobTemplate" => { - "metadata" => job_metadata, - "spec" => job_spec - } - } + "spec" => build_cron_job_spec } end - # rubocop:enable Metrics/MethodLength private @@ -44,6 +37,19 @@ def namespace "default" end + def build_cron_job_spec + default_spec = { + "schedule" => schedule, + "concurrencyPolicy" => "Forbid", + "jobTemplate" => { + "metadata" => job_metadata, + "spec" => job_spec + } + } + + default_spec.merge(cron_job_settings) + end + def job_spec spec = job_manifest["spec"].dup first_container = spec["template"]["spec"]["containers"][0] diff --git a/lib/cron_kubernetes/scheduler.rb b/lib/cron_kubernetes/scheduler.rb index 8d8e568..8ff46ea 100644 --- a/lib/cron_kubernetes/scheduler.rb +++ b/lib/cron_kubernetes/scheduler.rb @@ -44,12 +44,12 @@ def make_command(command) def new_cron_job(schedule, command, name, cron_job_settings) CronJob.new( + command: make_command(command), schedule:, - command: make_command(command), - job_manifest: CronKubernetes.manifest, + job_manifest: CronKubernetes.manifest, cron_job_settings:, name:, - identifier: @identifier + identifier: @identifier ) end diff --git a/spec/cron_kubernetes/cron_job_spec.rb b/spec/cron_kubernetes/cron_job_spec.rb index 0db12b3..3848326 100644 --- a/spec/cron_kubernetes/cron_job_spec.rb +++ b/spec/cron_kubernetes/cron_job_spec.rb @@ -24,11 +24,11 @@ it "accepts schedule, command, job_manifest, name parameters" do job = CronKubernetes::CronJob.new( - schedule: "30 0 * * *", - command: "/bin/bash -l -c ls\\ -l", + schedule: "30 0 * * *", + command: "/bin/bash -l -c ls\\ -l", job_manifest: manifest, - name: "cron-job", - identifier: "my-app" + name: "cron-job", + identifier: "my-app" ) expect(job.schedule).to eq "30 0 * * *" expect(job.command).to eq "/bin/bash -l -c ls\\ -l" @@ -68,11 +68,11 @@ context "#cron_job_manifest" do subject do CronKubernetes::CronJob.new( - schedule: "*/1 * * * *", - command: ["/bin/bash", "-l", "-c", "echo Hello from the Kubernetes cluster"], + schedule: "*/1 * * * *", + command: ["/bin/bash", "-l", "-c", "echo Hello from the Kubernetes cluster"], job_manifest: manifest, - name: "hello", - identifier: "my-app" + name: "hello", + identifier: "my-app" ) end @@ -109,10 +109,10 @@ context "when no name is provided" do subject do CronKubernetes::CronJob.new( - schedule: "*/1 * * * *", - command: ["/bin/bash", "-l", "-c", "echo Hello from the Kubernetes cluster"], + schedule: "*/1 * * * *", + command: ["/bin/bash", "-l", "-c", "echo Hello from the Kubernetes cluster"], job_manifest: manifest, - identifier: "my-app" + identifier: "my-app" ) end diff --git a/spec/cron_kubernetes/cron_tab_spec.rb b/spec/cron_kubernetes/cron_tab_spec.rb index 72af652..1916f62 100644 --- a/spec/cron_kubernetes/cron_tab_spec.rb +++ b/spec/cron_kubernetes/cron_tab_spec.rb @@ -19,20 +19,20 @@ let(:cron_job_manifest) do { apiVersion: "batch/v1", - kind: "CronJob", - metadata: { - name: "spec-minutely", + kind: "CronJob", + metadata: { + name: "spec-minutely", namespace: "default", - labels: {"cron-kubernetes-identifier": "spec"} + labels: {"cron-kubernetes-identifier": "spec"} }, - spec: { - schedule: "*/1 * * * *", + spec: { + schedule: "*/1 * * * *", jobTemplate: { metadata: nil, - spec: { + spec: { template: { spec: { - containers: [{image: "ubuntu", command: "ls -l"}], + containers: [{image: "ubuntu", command: "ls -l"}], restartPolicy: "OnFailure" } } @@ -43,11 +43,11 @@ end let(:job) do CronKubernetes::CronJob.new( - schedule: "*/1 * * * *", - command: "ls -l", + schedule: "*/1 * * * *", + command: "ls -l", job_manifest:, - name: "minutely", - identifier: "spec" + name: "minutely", + identifier: "spec" ) end let(:cron_job) do