miniexec 0.0.9 → 0.2.2

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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/bin/miniexec +18 -6
  3. data/lib/miniexec.rb +141 -104
  4. data/lib/util.rb +19 -0
  5. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f159f73abf46bbc302ba16f34e5f3ec7eac0fe530e57e06e24e3504f198f2027
4
- data.tar.gz: 41bd239c0271b589f448de12318397324f2e6ab67e6274caaa79405668119ef7
3
+ metadata.gz: 69219c3771eda716d56f9ee00bb7d8a7a9c214b38a35d17d2c14e297a0887420
4
+ data.tar.gz: 5ad61f25f0f1f757325e3e05be347ab3eae5d1861853c67ef938cc286fbdecd8
5
5
  SHA512:
6
- metadata.gz: a86c5c2d96807c1572d2a78a6799e05694987f2bee706fb7b9822a6ee0b6d5481bcc43e4aa3a668957b476a30d6dfb5f054aa37956948cd3e9b8d2dac336af4d
7
- data.tar.gz: 8722200cb309d02e28272ef2977464c1870980442deafacaa0dbd55842eb0fcfab77c0e33bf86bbd2ab18b7c39d48cf67fedbac3225b0ad84848436f36fbfffc
6
+ metadata.gz: d2413f6f8cf00ee930073576d7f82272bb46927ae74bbe0877ab49564ffe37ea2ca5b7183ae62fbc0fe58025d53877cd585b0ee30313116ab67f7aac553a71eb
7
+ data.tar.gz: 6b99388154b691babbd489e16e8ea54e711b9451776e99792cdcf9e3bea8798e7295abf6eabb018d7fb5a0f99d444f0d0b481969743e996d7cd2c18a287cf6e2
data/bin/miniexec CHANGED
@@ -10,7 +10,9 @@ options = {
10
10
  binds: [],
11
11
  env: {},
12
12
  path: '.',
13
- docker: ENV['DOCKER_HOST'] || '/run/docker.sock'
13
+ docker: ENV['DOCKER_HOST'] || '/run/docker.sock',
14
+ cwd: true,
15
+ file: nil
14
16
  }
15
17
 
16
18
  OptionParser.new do |opts|
@@ -40,13 +42,23 @@ OptionParser.new do |opts|
40
42
  'Location of the docker socket') do |sock|
41
43
  options[:docker] = sock
42
44
  end
45
+ opts.on('-f', '--file FILE',
46
+ 'Manually specify a custom .gitlab-ci.yml file.') do |file|
47
+ options[:file] = file
48
+ end
49
+ opts.on('-n', '--no-mount-cwd',
50
+ 'Don\'t mount the CWD in the container\'s WORKDIR by default.') do
51
+ options[:cwd] = false
52
+ end
43
53
  end.parse!
44
54
 
45
55
  raise OptionParser::MissingArgument, 'Specify a job with -j' if options[:job].nil?
46
56
 
47
- MiniExec.config(project_path: options[:path])
48
- exec = MiniExec.new options[:job],
49
- docker_url: options[:docker],
50
- binds: options[:binds],
51
- env: options[:env]
57
+ MiniExec::MiniExec.config(project_path: options[:path])
58
+ exec = MiniExec::MiniExec.new options[:job],
59
+ docker_url: options[:docker],
60
+ binds: options[:binds],
61
+ env: options[:env],
62
+ mount_cwd: options[:cwd] || false,
63
+ file: options[:file]
52
64
  exec.run_job
data/lib/miniexec.rb CHANGED
@@ -1,124 +1,161 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Main class
4
- class MiniExec
5
- require 'logger'
6
- require 'docker-api'
7
- require 'json'
8
- require 'tempfile'
9
- require 'yaml'
10
- require 'git'
11
- # Class instance variables
12
- @project_path = '.'
13
- @workflow_file = '.gitlab-ci.yml'
4
+ module MiniExec
5
+ class MiniExec
6
+ require 'logger'
7
+ require 'docker-api'
8
+ require 'json'
9
+ require 'tempfile'
10
+ require 'yaml'
11
+ require 'git'
12
+ require_relative './util'
13
+ # Class instance variables
14
+ @project_path = '.'
15
+ @workflow_file = '.gitlab-ci.yml'
14
16
 
15
- class << self
16
- attr_accessor :project_path, :workflow_file
17
- end
17
+ class << self
18
+ attr_accessor :project_path, :workflow_file
19
+ end
18
20
 
19
- def self.config(project_path: @project_path, workflow_file: @workflow_file)
20
- @project_path = project_path
21
- @workflow_file = workflow_file
22
- self
23
- end
21
+ def self.config(project_path: @project_path, workflow_file: @workflow_file)
22
+ @project_path = project_path
23
+ @workflow_file = workflow_file
24
+ self
25
+ end
24
26
 
25
- attr_accessor :script
27
+ attr_accessor :script
28
+ attr_reader :runlog
26
29
 
27
- def initialize(job,
28
- project_path: self.class.project_path,
29
- docker_url: nil,
30
- binds: [],
31
- env: {})
32
- @job_name = job
33
- @project_path = project_path
34
- @workflow = YAML.load(File.read("#{@project_path}/#{MiniExec.workflow_file}"))
35
- @job = @workflow[job]
36
- @job['name'] = job
37
- @default_image = @workflow['image'] || 'debian:buster-slim'
38
- @image = set_job_image
39
- @script = compile_script
40
- @binds = binds
41
- @env = {}
42
- @env.merge! env
43
- @env.merge! gitlab_env
44
- @env.merge! variables
45
- configure_logger
46
- Docker.options[:read_timeout] = 6000
47
- Docker.url = docker_url if docker_url
48
- end
30
+ def initialize(job,
31
+ project_path: self.class.project_path,
32
+ docker_url: nil,
33
+ binds: [],
34
+ env: {},
35
+ mount_cwd: true,
36
+ file: nil)
37
+ @job_name = job
38
+ @project_path = project_path
39
+ file ||= "#{@project_path}/#{MiniExec.workflow_file}"
40
+ @workflow = YAML.load(File.read(file))
41
+ @job = @workflow[job]
42
+ @job['name'] = job
43
+ @default_image = @workflow['image'] || 'debian:buster-slim'
44
+ @image = set_job_image
45
+ @entrypoint = set_job_entrypoint
46
+ @binds = binds
47
+ @mount_cwd = mount_cwd
48
+ @env = {}
49
+ [
50
+ env,
51
+ gitlab_env,
52
+ @workflow['variables'],
53
+ @job['variables']
54
+ ].each do |var_set|
55
+ @env.merge!(var_set.transform_values { |v| Util.expand_var(v.to_s, @env) }) if var_set
56
+ end
57
+ @script = compile_script
58
+ @runlog = []
59
+ configure_logger
60
+ Docker.options[:read_timeout] = 6000
61
+ Docker.url = docker_url if docker_url
62
+ end
63
+
64
+ def run_job
65
+ script_path = "/tmp/#{@job['name']}.sh"
66
+ @logger.info "Fetching image #{@image}"
67
+ Docker::Image.create(fromImage: @image)
68
+ @logger.info 'Image fetched'
49
69
 
50
- def run_job
51
- script_path = "/tmp/#{@job['name']}.sh"
52
- @logger.info "Fetching image #{@image}"
53
- Docker::Image.create(fromImage: @image)
54
- @logger.info 'Image fetched'
55
- Dir.chdir(@project_path) do
56
- @logger.info 'Creating container'
57
- container = Docker::Container.create(
58
- Cmd: ['/bin/bash', script_path],
59
- Image: @image,
60
- Volumes: @binds.map { |b| { b => { path_parent: 'rw' } } }.inject(:merge),
61
- Env: @env.map { |k, v| "#{k}=#{v}" }
62
- )
63
- container.store_file(script_path, @script)
64
- container.start({ Binds: [@binds] })
65
- container.tap(&:start).attach { |_, chunk| puts chunk }
70
+ config = Docker::Image.get(@image).info['Config']
71
+ working_dir = if config['WorkingDir'].empty?
72
+ '/gitlab'
73
+ else
74
+ config['WorkingDir']
75
+ end
76
+ binds = @binds
77
+ binds.push "#{ENV['PWD']}:#{working_dir}" if @mount_cwd
78
+ Dir.chdir(@project_path) do
79
+ @logger.info 'Creating container'
80
+ container = Docker::Container.create(
81
+ Image: @image,
82
+ Cmd: ['/usr/bin/env', 'bash', script_path],
83
+ WorkingDir: working_dir,
84
+ Entrypoint: @entrypoint,
85
+ Volumes: binds.map { |b| { b => { path_parent: 'rw' } } }.inject(:merge),
86
+ Env: @env.map { |k, v| "#{k}=#{v}" }
87
+ )
88
+ container.store_file(script_path, @script)
89
+ container.start({ Binds: [@binds] })
90
+ container.tap(&:start).attach { |_, chunk| puts chunk; @runlog.push chunk}
91
+ @logger.info 'Job finished. Removing container.'
92
+ # After running, we want to remove the container.
93
+ container.remove
94
+ @logger.info 'Container removed.'
95
+ end
66
96
  end
67
- end
68
97
 
69
- private
98
+ private
70
99
 
71
- def set_job_image
72
- return @job['image'] if @job['image']
100
+ def set_job_image
101
+ if @job['image']
102
+ image = @job['image'] if @job['image'].instance_of?(String)
103
+ image = @job['image']['name'] if @job['image'].instance_of?(Hash)
104
+ end
73
105
 
74
- @default_image
75
- end
106
+ image || @default_image
107
+ end
76
108
 
77
- # Set gitlab's predefined env vars as per
78
- # https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
79
- def gitlab_env
80
- g = Git.open(@project_path)
81
- commit = g.gcommit 'HEAD'
82
- tag = g.tags.find { |t| t.objectish == commit.sha }
83
- commit_branch = g.branch.name
84
- if tag.nil?
85
- ref_name = g.branch.name
86
- commit_tag = nil
87
- else
88
- ref_name = tag.name
89
- commit_tag = ref_name
109
+ def set_job_entrypoint
110
+ @job['image']['entrypoint'] if @job['image'].instance_of?(Hash)
90
111
  end
91
- {
92
- 'CI': true,
93
- 'CI_COMMIT_REF_SHA': commit.sha,
94
- 'CI_COMMIT_SHORT_SHA': commit.sha[0, 8],
95
- 'CI_COMMIT_REF_NAME': ref_name,
96
- 'CI_COMMIT_BRANCH': commit_branch,
97
- 'CI_COMMIT_TAG': commit_tag,
98
- 'CI_COMMIT_MESSAGE': commit.message,
99
- 'CI_COMMIT_REF_PROTECTED': false,
100
- 'CI_COMMIT_TIMESTAMP': commit.date.strftime('%FT%T')
101
- }.transform_keys(&:to_s)
102
- end
103
112
 
104
- def variables
105
- globals = @workflow['variables'] || {}
106
- job_locals = @job['variables'] || {}
107
- globals.merge job_locals
108
- end
113
+ # Set gitlab's predefined env vars as per
114
+ # https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
115
+ def gitlab_env
116
+ g = Git.open(@project_path)
117
+ commit = g.gcommit 'HEAD'
118
+ tag = g.tags.find { |t| t.objectish == commit.sha }
119
+ commit_branch = g.branch.name
120
+ if tag.nil?
121
+ ref_name = g.branch.name
122
+ commit_tag = nil
123
+ else
124
+ ref_name = tag.name
125
+ commit_tag = ref_name
126
+ end
127
+ {
128
+ 'CI': true,
129
+ 'CI_COMMIT_REF_SHA': commit.sha,
130
+ 'CI_COMMIT_SHORT_SHA': commit.sha[0, 8],
131
+ 'CI_COMMIT_REF_NAME': ref_name,
132
+ 'CI_COMMIT_BRANCH': commit_branch,
133
+ 'CI_COMMIT_TAG': commit_tag,
134
+ 'CI_COMMIT_MESSAGE': commit.message,
135
+ 'CI_COMMIT_REF_PROTECTED': false,
136
+ 'CI_COMMIT_TIMESTAMP': commit.date.strftime('%FT%T')
137
+ }.transform_keys(&:to_s)
138
+ end
109
139
 
110
- def configure_logger
111
- @logger = Logger.new($stdout)
112
- @logger.formatter = proc do |severity, _, _, msg|
113
- "[#{severity}]: #{msg}\n"
140
+ def variables
141
+ globals = @workflow['variables'] || {}
142
+ job_locals = @job['variables'] || {}
143
+ globals.merge job_locals
144
+ end
145
+
146
+ def configure_logger
147
+ @logger = Logger.new($stdout)
148
+ @logger.formatter = proc do |severity, _, _, msg|
149
+ "[#{severity}]: #{msg}\n"
150
+ end
151
+ @logger.level = ENV['LOGLEVEL'] || Logger::INFO
114
152
  end
115
- @logger.level = ENV['LOGLEVEL'] || Logger::INFO
116
- end
117
153
 
118
- def compile_script
119
- before_script = @job['before_script'] || ''
120
- script = @job['script'] || ''
121
- after_script = @job['after_script'] || ''
122
- [before_script, script, after_script].flatten.join("\n").strip
154
+ def compile_script
155
+ before_script = @job['before_script'] || ''
156
+ script = @job['script'] || ''
157
+ after_script = @job['after_script'] || ''
158
+ [before_script, script, after_script].flatten.join("\n").strip
159
+ end
123
160
  end
124
161
  end
data/lib/util.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniExec
4
+ module Util
5
+ # Given a string and an env, replace any instance of shell-style variables
6
+ # with their value in the env. NOT POSIX COMPLIANT, just mostly hacky so
7
+ # I can get gitlab-ci.yml parsing to work properly. Required in MiniExec
8
+ # because of https://docs.gitlab.com/ee/ci/variables/where_variables_can_be_used.html#gitlab-internal-variable-expansion-mechanism
9
+ def self.expand_var(string, env)
10
+ # Match group 1 = the text to replace
11
+ # Match group 2 = the key from env we want to replace it with
12
+ regex = /(\${?(\w+)}?)/
13
+ string.scan(regex).uniq.each do |match|
14
+ string.gsub! match[0], env[match[1]].to_s
15
+ end
16
+ string
17
+ end
18
+ end
19
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: miniexec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Pugh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-27 00:00:00.000000000 Z
11
+ date: 2021-06-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A minimal interpretor/executor for .gitlab-ci.yml
14
14
  email: pugh@s3kr.it
@@ -19,6 +19,7 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - bin/miniexec
21
21
  - lib/miniexec.rb
22
+ - lib/util.rb
22
23
  homepage: https://github.com/s3krit/miniexec
23
24
  licenses:
24
25
  - AGPL-3.0-or-later