miniexec 0.0.9 → 0.2.2

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