drcheckr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []