drcheckr 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: 00f1d677aa2849da5fb4eb2e50da96cade410467e1071bc9c3dc8ae4dd118b07
4
+ data.tar.gz: b38da04f13466a728631cc0a17797502d2b02a645d91b4bd89348fb7f47575cf
5
+ SHA512:
6
+ metadata.gz: 2286c9b137ac7f1cbda269c72feaa8a1b6cf847aa33e075448bb5ba3bf88a3e31c401349a01ed42a10814423f3555fb402dcb7b3863a6d36db6ef4c72d89ba00
7
+ data.tar.gz: 01d69f20ddf2ac11b6577c8774b5e67be5918dc713023b39d65c22fd547b8de6c674837bb6683aa4e8ede9f05c7feca6daf709fae64f02bfb76ef9653d0e0aba
data/bin/drcheckr ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ lib_dir = File.expand_path("#{__dir__}/../lib")
6
+ $:.unshift(lib_dir)
7
+ end
8
+
9
+ require 'drcheckr'
10
+
11
+ Drcheckr::Cli.start(ARGV)
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drcheckr
4
+ class Checkrfile
5
+ attr_reader :path
6
+
7
+ def initialize(path)
8
+ @path = path
9
+ end
10
+
11
+ def lockfile?
12
+ File.file?(lockfile_path)
13
+ end
14
+
15
+ def full_path
16
+ @full_path ||= begin
17
+ path = @path
18
+ path = "#{path}/.drcheckr.yml" if File.directory?(path)
19
+ File.expand_path(path)
20
+ end
21
+ end
22
+
23
+ def lockfile_path
24
+ @lock_file_name ||= begin
25
+ base = File.basename(full_path)
26
+ ext = File.extname(base)
27
+ prefix = base[0..(-ext.length-1)]
28
+ lock_fn = "#{prefix}-lock#{ext}"
29
+ File.expand_path(File.join full_path, "..", lock_fn)
30
+ end
31
+ end
32
+
33
+ def variables
34
+ @variables ||= begin
35
+ YAML.safe_load(File.read full_path).fetch('variables', [])
36
+ end
37
+ end
38
+
39
+ def images
40
+ @images ||= begin
41
+ YAML.safe_load(File.read full_path).fetch('images')
42
+ end
43
+ end
44
+
45
+ def dependencies
46
+ @dependencies ||= begin
47
+ YAML.safe_load(File.read full_path).fetch('dependencies')
48
+ end
49
+ end
50
+
51
+ def locked_versions
52
+ return nil unless lockfile?
53
+
54
+ @locked_versions ||= begin
55
+ YAML.safe_load(File.read lockfile_path).fetch('dependencies')
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drcheckr
4
+ class Cli
5
+ def self.start(args)
6
+ new(args).run
7
+ end
8
+
9
+ def initialize(args)
10
+ @args = args
11
+ @options = {
12
+ checkrfile_path: nil,
13
+ variables: {}
14
+ }
15
+ end
16
+
17
+ def run
18
+ command = nil
19
+ inline_args = []
20
+
21
+ loop do
22
+ str = @args.shift
23
+ break unless str
24
+
25
+ if str == 'help' || str == '--help'
26
+ print_help
27
+ exit(0)
28
+ end
29
+
30
+ if str == '--set'
31
+ name, value = @args.shift.split('=', 2)
32
+ @options[:variables][name.to_sym] = value
33
+ end
34
+
35
+ if command
36
+ if command == 'expr'
37
+ inline_args << str
38
+ else
39
+ puts "Error: can't run multiple commands at once."
40
+ print_help
41
+ exit(1)
42
+ end
43
+ else
44
+ command = str
45
+ end
46
+ end
47
+
48
+ unless command
49
+ puts "Error: no command was specified"
50
+ print_help
51
+ exit(1)
52
+ end
53
+
54
+ case command
55
+ when 'check'
56
+ update_check
57
+ when 'gen'
58
+ gen_dockerfile
59
+ when 'update'
60
+ update_lockfile
61
+ when 'expr'
62
+ print_expression(inline_args)
63
+ else
64
+ puts "Error: unrecognized command: #{command}"
65
+ print_help
66
+ exit(1)
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def print_help
73
+ puts "Usage: drcheckr <command> [options]"
74
+ puts "\nAvailable commands:"
75
+ puts " check - Checks whether dependencies are up to date"
76
+ puts " update - Like check, but persists new versions to the lock file"
77
+ puts " gen - Generates Dockerfile from existing lock file"
78
+ puts " expr <expression> - Evaluates and prints the given expression"
79
+ puts "\nOptions:"
80
+ puts " --set var=value - Can be used multiple times. Sets variable to the given value"
81
+ end
82
+
83
+ def checkrfile_path
84
+ @options[:checkrfile_path] || ENV.fetch('CHECKRFILE', 'drcheckr.yml')
85
+ end
86
+
87
+ def update_lockfile
88
+ c = Drcheckr::Commands::UpdateChecker.new(Checkrfile.new(checkrfile_path))
89
+ c.update!
90
+ end
91
+
92
+ def update_check
93
+ c = Drcheckr::Commands::UpdateChecker.new(Checkrfile.new(checkrfile_path))
94
+ c.check!
95
+ end
96
+
97
+ def print_expression(args)
98
+ if args.length != 1
99
+ puts "\"expr\" command expects one argument, but #{args.length} were given."
100
+ puts "aborting."
101
+ exit(1)
102
+ end
103
+
104
+ c = Drcheckr::Commands::ExpressionEvaluator.new(Checkrfile.new(checkrfile_path),
105
+ additional_vars: @options[:variables], expression: args.first)
106
+ c.run!
107
+ end
108
+
109
+ def gen_dockerfile
110
+ c = Drcheckr::Commands::GenDockerfile.new(Checkrfile.new(checkrfile_path),
111
+ additional_vars: @options[:variables])
112
+ c.run!
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drcheckr
4
+ module Commands
5
+ class ExpressionEvaluator
6
+ def initialize(checkrfile, additional_vars: {}, expression:)
7
+ @checkrfile = checkrfile
8
+ @additional_vars = additional_vars
9
+ @expression = expression
10
+ end
11
+
12
+ def run!
13
+ context_data = ContextData.new(@checkrfile)
14
+
15
+ template = Tilt::ERBTemplate.new { @expression }
16
+ output = template.render(context_data.assemble(additional_vars: @additional_vars))
17
+ print output
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drcheckr
4
+ module Commands
5
+ class GenDockerfile
6
+ attr_reader :checkrfile
7
+ attr_reader :params
8
+
9
+ def initialize(checkrfile, additional_vars:)
10
+ @checkrfile = checkrfile
11
+ @additional_vars = additional_vars
12
+ end
13
+
14
+ def run!
15
+ unless @checkrfile.lockfile?
16
+ puts "No lockfile found, aborting"
17
+ exit 1
18
+ end
19
+
20
+ missing_version = @checkrfile.dependencies.map{ |x| x['name'] } - @checkrfile.locked_versions.keys
21
+ unless missing_version.empty?
22
+ puts "Missing versions for the following dependencies:"
23
+ missing_version.each do |dep_name|
24
+ puts " - #{dep_name}"
25
+ end
26
+ puts "Can't proceed, aborting."
27
+ exit 1
28
+ end
29
+
30
+ puts "Will generate the following images:"
31
+ @checkrfile.images.each do |img|
32
+ puts "- #{img['name']}"
33
+ end
34
+
35
+ @checkrfile.images.each do |img|
36
+ image = img['name']
37
+ generator = Generator::ContainerFileGenerator.new(@checkrfile, image)
38
+ generator.generate
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drcheckr
4
+ module Commands
5
+ class UpdateChecker
6
+ attr_reader :checkrfile
7
+
8
+ def initialize(checkrfile)
9
+ @checkrfile = checkrfile
10
+ end
11
+
12
+ def update!
13
+ check_pass
14
+
15
+ new_lock_file = checkrfile.locked_versions.dup
16
+ @outdated_list.each do |dep_name|
17
+ latest_version = @all_versions[dep_name].first
18
+ puts "Will update #{dep_name} to #{latest_version}"
19
+ new_lock_file[dep_name] = latest_version
20
+ end
21
+
22
+ if @outdated_list.empty?
23
+ puts "Everything up to date, no action needed!"
24
+ return
25
+ end
26
+ puts "Updating #{@outdated_list.length} dependencies"
27
+
28
+ dest_file = checkrfile.lockfile_path
29
+ data = YAML.dump({ 'dependencies' => new_lock_file })
30
+ File.write(dest_file, data)
31
+ puts "Wrote new dependencies to #{dest_file}"
32
+ end
33
+
34
+ def check!
35
+ if !checkrfile.lockfile?
36
+ puts "Lock file not present, can't check"
37
+ exit 1
38
+ end
39
+
40
+ check_pass
41
+
42
+ exit(0) if @outdated_list.empty?
43
+
44
+ puts("Found #{@outdated_list.length} outdated dependencies")
45
+ exit(122)
46
+ end
47
+
48
+ private
49
+
50
+ def check_pass
51
+ @all_versions = {}
52
+ @outdated_list = []
53
+ checkrfile.dependencies.each do |defn|
54
+ m = defn.key?('github') ? 'github' : nil
55
+ m ||= defn.key?('fixed_version') ? 'fixed_version' : nil
56
+ m ||= defn.key?('google_chrome') ? 'google_chrome' : nil
57
+ m ||= defn.key?('eol') ? 'eol' : nil
58
+
59
+ if !m
60
+ raise "Can't track dependency: #{defn}"
61
+ else
62
+ send("update_check_#{m}", defn)
63
+ end
64
+ end
65
+ end
66
+
67
+ def finish_check_dep_version(defn, versions)
68
+ undefined = Object.new
69
+ dep_name = defn['name']
70
+
71
+ @all_versions[dep_name] = versions
72
+
73
+ curr_version = (checkrfile.locked_versions || {})[dep_name]
74
+ if !curr_version
75
+ puts "No lock for dependency '#{dep_name}'"
76
+ curr_version = undefined
77
+ end
78
+
79
+ latest_version = versions[0]
80
+ if curr_version != latest_version
81
+ @outdated_list << dep_name
82
+
83
+ puts "Updates available for #{dep_name}!"
84
+ puts "\tCurrent version: #{curr_version == undefined ? '<undefined>' : curr_version}"
85
+ puts "\tLatest version: #{latest_version}"
86
+ else
87
+ puts "Dependency #{dep_name} is up to date"
88
+ end
89
+ end
90
+
91
+ def update_check_fixed_version(defn)
92
+ dep_name = defn['name']
93
+ version = defn['fixed_version']
94
+ puts "Skip check for #{dep_name}, fixed at #{version}"
95
+
96
+ finish_check_dep_version(defn, [version])
97
+ end
98
+
99
+ def update_check_google_chrome(defn)
100
+ platform, channel = defn['google_chrome'].split('/')
101
+ channel ||= 'stable'
102
+ resp = Excon.get("https://versionhistory.googleapis.com/v1/chrome/platforms/#{platform}/channels/#{channel}/versions")
103
+ data = JSON.parse(resp.body)
104
+ versions = data['versions'].map{ |x| x['version'] }
105
+ finish_check_dep_version(defn, versions)
106
+ end
107
+
108
+ def update_check_eol(defn)
109
+ name = defn['eol']
110
+ cycle = defn['cycle']
111
+
112
+ resp = Excon.get("https://endoflife.date/api/#{name}.json")
113
+ data = JSON.parse(resp.body)
114
+ data.select! { |x| x['cycle'] == cycle } if cycle
115
+
116
+ versions = data.map { |x| x['latest'] }
117
+ finish_check_dep_version(defn, versions)
118
+ end
119
+
120
+ def update_check_github(defn)
121
+ dep_name = defn['name']
122
+ repo_slug = defn['github']
123
+ puts "GH check #{dep_name}"
124
+
125
+ url = "https://github.com/#{repo_slug}/releases.atom"
126
+ resp = Excon.get(url, {
127
+ headers: {
128
+ 'User-Agent' => "drcheckr/#{Drcheckr::VERSION}",
129
+ 'Accept' => 'application/atom+xml',
130
+ }
131
+ })
132
+
133
+ doc = Nokogiri::XML(resp.body)
134
+ versions = []
135
+ doc.css('entry').each do |entry|
136
+ id = entry.css('id').text.strip
137
+ version = id.split('/').last
138
+
139
+ # puts "\t version |#{version}|"
140
+ versions << version
141
+ end
142
+
143
+ finish_check_dep_version(defn, versions)
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drcheckr
4
+ class ContextData
5
+ def initialize(checkrfile)
6
+ @checkrfile = checkrfile
7
+ @data = {}
8
+ end
9
+
10
+ def []=(key, value)
11
+ @data[key.to_sym] = value
12
+ end
13
+
14
+ def assemble(**kw)
15
+ OpenStruct.new(to_h(**kw))
16
+ end
17
+
18
+ def to_h(additional_vars: {})
19
+ cdata = @data.dup
20
+
21
+ @checkrfile.locked_versions.each do |dep_name, version|
22
+ cdata["#{dep_name}_version".to_sym] = VersionNumber.parse(version)
23
+ end
24
+
25
+ cdata.merge! VariableLoader.new(@checkrfile).load(cdata, additional_vars)
26
+
27
+ cdata
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drcheckr
4
+ module Generator
5
+ class ContainerFileGenerator
6
+ def initialize(checkrfile, image_name, additional_vars: {})
7
+ @image_name = image_name
8
+ @checkrfile = checkrfile
9
+ @additional_vars = additional_vars
10
+ end
11
+
12
+ def generate
13
+ template = Tilt.new(template_path)
14
+
15
+ context_data = ContextData.new(@checkrfile)
16
+ context_data[:standard_banner] = make_standard_banner
17
+
18
+ final = template.render(context_data.assemble(additional_vars: @additional_vars))
19
+
20
+ dest_file = File.expand_path("#{@image_name}/Dockerfile")
21
+ File.write(dest_file, final)
22
+ end
23
+
24
+ private
25
+
26
+ def template_path
27
+ File.expand_path("#{@image_name}/Dockerfile.erb")
28
+ end
29
+
30
+ def make_standard_banner
31
+ header = ["GENERATED FILE, DO NOT EDIT!"]
32
+ header << "This was generated using drcheckr"
33
+ header << ""
34
+ header << "Dependencies:"
35
+ @checkrfile.locked_versions.each do |dep_name, version|
36
+ header << " - #{dep_name}: #{version}"
37
+ end
38
+
39
+ header << ""
40
+ header.map{ |x| "\# #{x}" }.join("\n")
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drcheckr
4
+ class VariableLoader
5
+ def initialize(checkrfile)
6
+ @checkrfile = checkrfile
7
+ end
8
+
9
+ def load(current_context, additional_vars={})
10
+ locked_vars = YAML.safe_load(File.read @checkrfile.lockfile_path).fetch('variables', {})
11
+
12
+ out_context = {}
13
+ computed = []
14
+ @checkrfile.variables.each do |defn|
15
+ name = defn['name'].to_sym
16
+ if defn.key?('computed')
17
+ computed << defn
18
+ elsif defn.key?('value')
19
+ out_context[name] = defn['value']
20
+ else
21
+ out_context[name] = locked_vars.fetch(name.to_s)
22
+ end
23
+ end
24
+
25
+ computed_ctx = {}
26
+ computed.each do |cdefn|
27
+ name = cdefn['name'].to_sym
28
+ expr = cdefn['computed']
29
+
30
+ value = Tilt::ERBTemplate.new { expr }.render(OpenStruct.new current_context.merge(out_context))
31
+
32
+ computed_ctx[name] = value
33
+ end
34
+ out_context.merge!(computed_ctx)
35
+
36
+ out_context
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,4 @@
1
+
2
+ module Drcheckr
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drcheckr
4
+ class VersionNumber
5
+ attr_reader :original
6
+ attr_reader :major, :minor, :patch
7
+
8
+ def self.parse(value)
9
+ new.tap { |x| x.parse(value) }
10
+ end
11
+
12
+ def to_s
13
+ @original
14
+ end
15
+
16
+ def semver(n=3)
17
+ mid = (["(\\d+)"] * n).join("\\.")
18
+ /^#{mid}$/.match(@original)
19
+ end
20
+
21
+ def semver?(n=3)
22
+ semver(n) != nil
23
+ end
24
+
25
+ def parse(value)
26
+ @original = value
27
+
28
+ begin
29
+ s = no_v.split(/[-\.]/)
30
+ @major = Integer(s[0])
31
+ @minor = s.length > 1 ? Integer(s[1]) : nil
32
+ @patch = s.length > 2 ? Integer(s[2]) : nil
33
+ rescue ArgumentError
34
+ end
35
+ end
36
+
37
+ def no_v
38
+ @original.start_with?('v') ? @original[1..] : @original
39
+ end
40
+ alias pure no_v
41
+
42
+ def major_minor
43
+ "#{@major}.#{@minor}"
44
+ end
45
+
46
+ def major_minor_patch
47
+ "#{@major}.#{@minor}.#{@patch}"
48
+ end
49
+ end
50
+ end
data/lib/drcheckr.rb ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'debug'
4
+
5
+ require 'nokogiri'
6
+ require 'excon'
7
+ require 'json'
8
+ require 'uri'
9
+ require 'yaml'
10
+ require 'tilt'
11
+ require 'tilt/erb'
12
+ require 'ostruct'
13
+ require 'time'
14
+ require 'date'
15
+
16
+ module Drcheckr
17
+ end
18
+
19
+ require 'drcheckr/version'
20
+ require 'drcheckr/cli'
21
+ require 'drcheckr/version_number'
22
+ require 'drcheckr/checkrfile'
23
+
24
+ require 'drcheckr/context_data'
25
+ require 'drcheckr/variable_loader'
26
+ require 'drcheckr/generator/container_file_generator'
27
+
28
+ require 'drcheckr/commands/update_checker'
29
+ require 'drcheckr/commands/gen_dockerfile'
30
+ require 'drcheckr/commands/expression_evaluator'
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: drcheckr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - André Piske
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-09-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: excon
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.111'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.111'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tilt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.4'
41
+ description: Tracks dependencies and generates Dockerfiles using ERB
42
+ email:
43
+ - andrepiske@gmail.com
44
+ executables:
45
+ - drcheckr
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - bin/drcheckr
50
+ - lib/drcheckr.rb
51
+ - lib/drcheckr/checkrfile.rb
52
+ - lib/drcheckr/cli.rb
53
+ - lib/drcheckr/commands/expression_evaluator.rb
54
+ - lib/drcheckr/commands/gen_dockerfile.rb
55
+ - lib/drcheckr/commands/update_checker.rb
56
+ - lib/drcheckr/context_data.rb
57
+ - lib/drcheckr/generator/container_file_generator.rb
58
+ - lib/drcheckr/variable_loader.rb
59
+ - lib/drcheckr/version.rb
60
+ - lib/drcheckr/version_number.rb
61
+ homepage: https://github.com/trusted/drcheckr
62
+ licenses:
63
+ - MIT
64
+ metadata:
65
+ rubygems_mfa_required: 'true'
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 3.2.0
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 3.5.17
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Dockerfile dependency tracker
85
+ test_files: []