miniexec 0.0.8 → 0.2.1

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 +12 -6
  3. data/lib/miniexec.rb +139 -101
  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: 1c459ebbf1b0b28551c9d8243d323552518f19594ebf1cd327db16a0e4a629c5
4
- data.tar.gz: 03c7bb377700b97f4bace54b20c1ca10d75be95f6e773e7347204d17fbed6a9a
3
+ metadata.gz: 550158d86f5f2784056c277e222f8b446df695deccd78dfa6e585ec4ff755852
4
+ data.tar.gz: e6b1ca3987365aabf54d3f126b153a0b527e1b5b2075395b57ecb3c240a046ca
5
5
  SHA512:
6
- metadata.gz: dceeeb43f8835bc188b7bf1f7dab09b0666563b04eb3428434debe5d1125b1962fe07c0efcba15685db7b8d27bb23927558cafecb6d55d20591fa29ee89e4431
7
- data.tar.gz: 61f032f5532532db3b935fea5538b0d5591bbe44d1cc63d8ac9a9d9d5335a5b15043dc8c52651131e0f444362a33e4c09a9b28963eadadff36050fe7c9272418
6
+ metadata.gz: c3483cdf517a9fec48c1c0146ead79f323986892420cc5a5d7bf918f8ff2f02ff7734ed324bf75df81b8b10e7258376b8ba185d6de3914f4aeccb550553832f7
7
+ data.tar.gz: 19b604234de06aa7276e15fa73ea00c2f6f4419ee3f6414b2c2768d5c1cae61bcc91b51370f639a48ab5cdc195a6c93dbf2efa01c0731ff8e99ff83a9f91a7d0
data/bin/miniexec CHANGED
@@ -10,7 +10,8 @@ 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
14
15
  }
15
16
 
16
17
  OptionParser.new do |opts|
@@ -40,13 +41,18 @@ OptionParser.new do |opts|
40
41
  'Location of the docker socket') do |sock|
41
42
  options[:docker] = sock
42
43
  end
44
+ opts.on('-n', '--no-mount-cwd',
45
+ 'Don\'t mount the CWD in the container\'s WORKDIR by default.') do
46
+ options[:cwd] = false
47
+ end
43
48
  end.parse!
44
49
 
45
50
  raise OptionParser::MissingArgument, 'Specify a job with -j' if options[:job].nil?
46
51
 
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]
52
+ MiniExec::MiniExec.config(project_path: options[:path])
53
+ exec = MiniExec::MiniExec.new options[:job],
54
+ docker_url: options[:docker],
55
+ binds: options[:binds],
56
+ env: options[:env],
57
+ mount_cwd: options[:cwd] || false
52
58
  exec.run_job
data/lib/miniexec.rb CHANGED
@@ -1,121 +1,159 @@
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 = env.merge gitlab_env, variables
42
- configure_logger
43
- Docker.options[:read_timeout] = 6000
44
- Docker.url = docker_url if docker_url
45
- 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
+ @job_name = job
37
+ @project_path = project_path
38
+ @workflow = YAML.load(File.read("#{@project_path}/#{MiniExec.workflow_file}"))
39
+ @job = @workflow[job]
40
+ @job['name'] = job
41
+ @default_image = @workflow['image'] || 'debian:buster-slim'
42
+ @image = set_job_image
43
+ @entrypoint = set_job_entrypoint
44
+ @binds = binds
45
+ @mount_cwd = mount_cwd
46
+ @env = {}
47
+ [
48
+ env,
49
+ gitlab_env,
50
+ @workflow['variables'],
51
+ @job['variables']
52
+ ].each do |var_set|
53
+ @env.merge!(var_set.transform_values { |v| Util.expand_var(v.to_s, @env) }) if var_set
54
+ end
55
+ @script = compile_script
56
+ @runlog = []
57
+ configure_logger
58
+ Docker.options[:read_timeout] = 6000
59
+ Docker.url = docker_url if docker_url
60
+ end
61
+
62
+ def run_job
63
+ script_path = "/tmp/#{@job['name']}.sh"
64
+ @logger.info "Fetching image #{@image}"
65
+ Docker::Image.create(fromImage: @image)
66
+ @logger.info 'Image fetched'
46
67
 
47
- def run_job
48
- script_path = "/tmp/#{@job['name']}.sh"
49
- @logger.info "Fetching image #{@image}"
50
- Docker::Image.create(fromImage: @image)
51
- @logger.info 'Image fetched'
52
- Dir.chdir(@project_path) do
53
- @logger.info 'Creating container'
54
- container = Docker::Container.create(
55
- Cmd: ['/bin/bash', script_path],
56
- Image: @image,
57
- Volumes: @binds.map { |b| { b => { path_parent: 'rw' } } }.inject(:merge),
58
- Env: @env.map { |k, v| "#{k}=#{v}" }
59
- )
60
- container.store_file(script_path, @script)
61
- container.start({ Binds: [@binds] })
62
- container.tap(&:start).attach { |_, chunk| puts chunk }
68
+ config = Docker::Image.get(@image).info['Config']
69
+ working_dir = if config['WorkingDir'].empty?
70
+ '/gitlab'
71
+ else
72
+ config['WorkingDir']
73
+ end
74
+ binds = @binds
75
+ binds.push "#{ENV['PWD']}:#{working_dir}" if @mount_cwd
76
+ Dir.chdir(@project_path) do
77
+ @logger.info 'Creating container'
78
+ container = Docker::Container.create(
79
+ Image: @image,
80
+ Cmd: ['/usr/bin/env', 'bash', script_path],
81
+ WorkingDir: working_dir,
82
+ Entrypoint: @entrypoint,
83
+ Volumes: binds.map { |b| { b => { path_parent: 'rw' } } }.inject(:merge),
84
+ Env: @env.map { |k, v| "#{k}=#{v}" }
85
+ )
86
+ container.store_file(script_path, @script)
87
+ container.start({ Binds: [@binds] })
88
+ container.tap(&:start).attach { |_, chunk| puts chunk; @runlog.push chunk}
89
+ @logger.info 'Job finished. Removing container.'
90
+ # After running, we want to remove the container.
91
+ container.remove
92
+ @logger.info 'Container removed.'
93
+ end
63
94
  end
64
- end
65
95
 
66
- private
96
+ private
67
97
 
68
- def set_job_image
69
- return @job['image'] if @job['image']
98
+ def set_job_image
99
+ if @job['image']
100
+ image = @job['image'] if @job['image'].instance_of?(String)
101
+ image = @job['image']['name'] if @job['image'].instance_of?(Hash)
102
+ end
70
103
 
71
- @default_image
72
- end
104
+ image || @default_image
105
+ end
73
106
 
74
- # Set gitlab's predefined env vars as per
75
- # https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
76
- def gitlab_env
77
- g = Git.open(@project_path)
78
- commit = g.gcommit 'HEAD'
79
- tag = g.tags.find { |t| t.objectish == commit.sha }
80
- commit_branch = g.branch.name
81
- if tag.nil?
82
- ref_name = g.branch.name
83
- commit_tag = nil
84
- else
85
- ref_name = tag.name
86
- commit_tag = ref_name
107
+ def set_job_entrypoint
108
+ @job['image']['entrypoint'] if @job['image'].instance_of?(Hash)
87
109
  end
88
- {
89
- 'CI': true,
90
- 'CI_COMMIT_REF_SHA': commit.sha,
91
- 'CI_COMMIT_SHORT_SHA': commit.sha[0, 8],
92
- 'CI_COMMIT_REF_NAME': ref_name,
93
- 'CI_COMMIT_BRANCH': commit_branch,
94
- 'CI_COMMIT_TAG': commit_tag,
95
- 'CI_COMMIT_MESSAGE': commit.message,
96
- 'CI_COMMIT_REF_PROTECTED': false,
97
- 'CI_COMMIT_TIMESTAMP': commit.date.strftime('%FT%T')
98
- }.transform_keys(&:to_s)
99
- end
100
110
 
101
- def variables
102
- globals = @workflow['variables'] || {}
103
- job_locals = @job['variables'] || {}
104
- globals.merge job_locals
105
- end
111
+ # Set gitlab's predefined env vars as per
112
+ # https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
113
+ def gitlab_env
114
+ g = Git.open(@project_path)
115
+ commit = g.gcommit 'HEAD'
116
+ tag = g.tags.find { |t| t.objectish == commit.sha }
117
+ commit_branch = g.branch.name
118
+ if tag.nil?
119
+ ref_name = g.branch.name
120
+ commit_tag = nil
121
+ else
122
+ ref_name = tag.name
123
+ commit_tag = ref_name
124
+ end
125
+ {
126
+ 'CI': true,
127
+ 'CI_COMMIT_REF_SHA': commit.sha,
128
+ 'CI_COMMIT_SHORT_SHA': commit.sha[0, 8],
129
+ 'CI_COMMIT_REF_NAME': ref_name,
130
+ 'CI_COMMIT_BRANCH': commit_branch,
131
+ 'CI_COMMIT_TAG': commit_tag,
132
+ 'CI_COMMIT_MESSAGE': commit.message,
133
+ 'CI_COMMIT_REF_PROTECTED': false,
134
+ 'CI_COMMIT_TIMESTAMP': commit.date.strftime('%FT%T')
135
+ }.transform_keys(&:to_s)
136
+ end
106
137
 
107
- def configure_logger
108
- @logger = Logger.new($stdout)
109
- @logger.formatter = proc do |severity, _, _, msg|
110
- "[#{severity}]: #{msg}\n"
138
+ def variables
139
+ globals = @workflow['variables'] || {}
140
+ job_locals = @job['variables'] || {}
141
+ globals.merge job_locals
142
+ end
143
+
144
+ def configure_logger
145
+ @logger = Logger.new($stdout)
146
+ @logger.formatter = proc do |severity, _, _, msg|
147
+ "[#{severity}]: #{msg}\n"
148
+ end
149
+ @logger.level = ENV['LOGLEVEL'] || Logger::INFO
111
150
  end
112
- @logger.level = ENV['LOGLEVEL'] || Logger::INFO
113
- end
114
151
 
115
- def compile_script
116
- before_script = @job['before_script'] || ''
117
- script = @job['script'] || ''
118
- after_script = @job['after_script'] || ''
119
- [before_script, script, after_script].flatten.join("\n").strip
152
+ def compile_script
153
+ before_script = @job['before_script'] || ''
154
+ script = @job['script'] || ''
155
+ after_script = @job['after_script'] || ''
156
+ [before_script, script, after_script].flatten.join("\n").strip
157
+ end
120
158
  end
121
159
  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.8
4
+ version: 0.2.1
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-05-17 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