Skip to content

Commit 7f9781e

Browse files
committed
(CDPE-6808) Add podman support
This commit adds support for podman. Instead of just executing docker, the task will look to see if either podman or docker are present, using the first one found. An environment variable is provided to give users a way to override the automatic detection if there is a special case we haven't thought of. Split out the runtime specific tests to separate specs, one for podman and another for docker to allow them to run on github runners with the appropriate runtime available.
1 parent 40bc850 commit 7f9781e

File tree

6 files changed

+319
-91
lines changed

6 files changed

+319
-91
lines changed

.github/workflows/unit-test.yml

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,36 @@ name: unit-test
22
on:
33
- pull_request
44
jobs:
5+
podman_tests:
6+
runs-on: ubuntu-22.04
7+
steps:
8+
- uses: actions/checkout@v2
9+
- uses: ruby/setup-ruby@v1
10+
with:
11+
ruby-version: 2.6
12+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
13+
- name: Uninstall docker
14+
run: |
15+
sudo apt-get update
16+
sudo apt-get remove -y docker-ce
17+
- name: Install podman
18+
run: |
19+
sudo apt-get update
20+
sudo apt-get install -y podman
21+
- run: bundle exec rspec spec/podman_spec.rb
22+
docker_tests:
23+
runs-on: ubuntu-22.04
24+
steps:
25+
- uses: actions/checkout@v2
26+
- uses: ruby/setup-ruby@v1
27+
with:
28+
ruby-version: 2.6
29+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
30+
- name: Uninstall podman
31+
run: |
32+
sudo apt-get update
33+
sudo apt-get remove -y podman
34+
- run: bundle exec rspec spec/docker_spec.rb
535
unix_tests:
636
runs-on: macos-latest
737
steps:
@@ -10,7 +40,7 @@ jobs:
1040
with:
1141
ruby-version: 2.6
1242
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
13-
- run: bundle exec rspec spec
43+
- run: bundle exec rspec spec/run_cd4pe_job_spec.rb
1444
windows_tests:
1545
runs-on: windows-latest
1646
steps:
@@ -19,4 +49,4 @@ jobs:
1949
with:
2050
ruby-version: 2.6
2151
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
22-
- run: $env:RUN_WINDOWS_UNIT_TESTS = "true"; bundle exec rspec spec
52+
- run: $env:RUN_WINDOWS_UNIT_TESTS = "true"; bundle exec rspec spec/run_cd4pe_job_spec.rb

spec/docker_spec.rb

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
require 'open3'
2+
require 'base64'
3+
require 'json'
4+
require 'fileutils'
5+
require_relative '../tasks/run_cd4pe_job.rb'
6+
7+
describe 'run_cd4pe_job' do
8+
before(:all) do
9+
@logger = Logger.new
10+
end
11+
12+
before(:each) do
13+
@working_dir = File.join(Dir.getwd, "test_working_dir")
14+
Dir.mkdir(@working_dir)
15+
16+
# Ensure tests don't write to /etc/containers/certs.d
17+
@certs_dir = File.join(@working_dir, "certs.d")
18+
CD4PEJobRunner.send(:remove_const, :DOCKER_CERTS)
19+
CD4PEJobRunner.const_set(:DOCKER_CERTS, @certs_dir)
20+
21+
@web_ui_endpoint = 'https://testtest.com'
22+
@job_token = 'alksjdbhfnadhsbf'
23+
@job_owner = 'carls cool carl'
24+
@job_instance_id = '17'
25+
@secrets = {
26+
secret1: "hello",
27+
secret2: "friend",
28+
}
29+
@windows_job = ENV['RUN_WINDOWS_UNIT_TESTS']
30+
end
31+
32+
after(:each) do
33+
FileUtils.remove_dir(@working_dir)
34+
$stdout = STDOUT
35+
end
36+
37+
describe 'cd4pe_job_helper::get_runtime' do
38+
it 'Detects docker as the available runtime.' do
39+
test_container_image = 'puppetlabs/test:10.0.1'
40+
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_container_image, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
41+
expect(job_helper.get_runtime).to eq('docker')
42+
end
43+
end
44+
45+
describe 'cd4pe_job_helper::update_docker_image' do
46+
let(:test_container_image) { 'puppetlabs/test:10.0.1' }
47+
it 'Generates a docker pull command.' do
48+
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_container_image, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
49+
docker_pull_command = job_helper.get_docker_pull_cmd
50+
expect(docker_pull_command).to eq("docker pull #{test_container_image}")
51+
end
52+
53+
context 'with config' do
54+
let(:hostname) { 'host1' }
55+
let(:creds_json) { {auths: {hostname => {}}}.to_json }
56+
let(:creds_b64) { Base64.encode64(creds_json) }
57+
let(:cert_txt) { 'junk' }
58+
let(:cert_b64) { Base64.encode64(cert_txt) }
59+
60+
it 'Uses config when present for docker.' do
61+
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_container_image, docker_pull_creds: creds_b64, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
62+
config_json = File.join(@working_dir, '.docker', 'config.json')
63+
expect(File.exist?(config_json)).to be(true)
64+
expect(File.read(config_json)).to eq(creds_json)
65+
66+
docker_pull_command = job_helper.get_docker_pull_cmd
67+
expect(docker_pull_command).to eq("docker --config #{File.join(@working_dir, '.docker')} pull #{test_container_image}")
68+
end
69+
70+
it 'Registers the CA cert when provided.' do
71+
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_container_image, docker_pull_creds: creds_b64, base_64_ca_cert: cert_b64, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
72+
73+
cert_file = File.join(@certs_dir, hostname, 'ca.crt')
74+
expect(File.exist?(cert_file)).to be(true)
75+
expect(File.read(cert_file)).to eq(cert_txt)
76+
end
77+
end
78+
end
79+
80+
describe 'cd4pe_job_helper::get_docker_run_cmd' do
81+
it 'Generates the correct docker run command.' do
82+
test_manifest_type = "AFTER_JOB_SUCCESS"
83+
test_container_image = 'puppetlabs/test:10.0.1'
84+
arg1 = '--testarg=woot'
85+
arg2 = '--otherarg=hello'
86+
arg3 = '--whatever=doesntmatter'
87+
user_specified_container_run_args = [arg1, arg2, arg3]
88+
job_type = 'unix'
89+
90+
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_container_image, docker_run_args: user_specified_container_run_args, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
91+
92+
docker_run_command = job_helper.get_docker_run_cmd(test_manifest_type)
93+
cmd_parts = docker_run_command.split(' ')
94+
95+
expect(cmd_parts[0]).to eq('docker')
96+
expect(cmd_parts[1]).to eq('run')
97+
expect(cmd_parts[2]).to eq('--rm')
98+
expect(cmd_parts[3]).to eq(arg1)
99+
expect(cmd_parts[4]).to eq(arg2)
100+
expect(cmd_parts[5]).to eq(arg3)
101+
expect(cmd_parts[6]).to eq('-e')
102+
expect(cmd_parts[7]).to eq('secret1')
103+
expect(cmd_parts[8]).to eq('-e')
104+
expect(cmd_parts[9]).to eq('secret2')
105+
expect(cmd_parts[10]).to eq('-v')
106+
expect(cmd_parts[11].end_with?("/#{File.basename(@working_dir)}/cd4pe_job/repo:/repo\"")).to be(true)
107+
expect(cmd_parts[12]).to eq('-v')
108+
expect(cmd_parts[13].end_with?("/#{File.basename(@working_dir)}/cd4pe_job/jobs/#{job_type}:/cd4pe_job\"")).to be(true)
109+
expect(cmd_parts[14]).to eq(test_container_image)
110+
expect(cmd_parts[15]).to eq('"/cd4pe_job/AFTER_JOB_SUCCESS"')
111+
end
112+
end
113+
end

spec/podman_spec.rb

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
require 'open3'
2+
require 'base64'
3+
require 'json'
4+
require 'fileutils'
5+
require_relative '../tasks/run_cd4pe_job.rb'
6+
7+
describe 'run_cd4pe_job' do
8+
before(:all) do
9+
@logger = Logger.new
10+
end
11+
12+
before(:each) do
13+
@working_dir = File.join(Dir.getwd, "test_working_dir")
14+
Dir.mkdir(@working_dir)
15+
16+
# Ensure tests don't write to /etc/containers/certs.d
17+
@certs_dir = File.join(@working_dir, "certs.d")
18+
CD4PEJobRunner.send(:remove_const, :PODMAN_CERTS)
19+
CD4PEJobRunner.const_set(:PODMAN_CERTS, @certs_dir)
20+
21+
@web_ui_endpoint = 'https://testtest.com'
22+
@job_token = 'alksjdbhfnadhsbf'
23+
@job_owner = 'carls cool carl'
24+
@job_instance_id = '17'
25+
@secrets = {
26+
secret1: "hello",
27+
secret2: "friend",
28+
}
29+
@windows_job = ENV['RUN_WINDOWS_UNIT_TESTS']
30+
end
31+
32+
after(:each) do
33+
FileUtils.remove_dir(@working_dir)
34+
$stdout = STDOUT
35+
end
36+
37+
describe 'cd4pe_job_helper::get_runtime' do
38+
it 'Detects podman as the available runtime.' do
39+
test_container_image = 'puppetlabs/test:10.0.1'
40+
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_container_image, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
41+
expect(job_helper.get_runtime).to eq('podman')
42+
end
43+
end
44+
45+
describe 'cd4pe_job_helper::update_docker_image' do
46+
let(:test_container_image) { 'puppetlabs/test:10.0.1' }
47+
it 'Generates a podman pull command.' do
48+
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_container_image, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
49+
podman_pull_command = job_helper.get_docker_pull_cmd
50+
expect(podman_pull_command).to eq("podman pull #{test_container_image}")
51+
end
52+
53+
context 'with config' do
54+
let(:hostname) { 'host1' }
55+
let(:creds_json) { {auths: {hostname => {}}}.to_json }
56+
let(:creds_b64) { Base64.encode64(creds_json) }
57+
let(:cert_txt) { 'junk' }
58+
let(:cert_b64) { Base64.encode64(cert_txt) }
59+
60+
it 'Uses config when present for podman.' do
61+
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_container_image, docker_pull_creds: creds_b64, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
62+
config_json = File.join(@working_dir, '.docker', 'config.json')
63+
expect(File.exist?(config_json)).to be(true)
64+
expect(File.read(config_json)).to eq(creds_json)
65+
66+
podman_pull_command = job_helper.get_docker_pull_cmd
67+
expect(podman_pull_command).to eq("podman --config #{File.join(@working_dir, '.docker')} pull #{test_container_image}")
68+
end
69+
70+
it 'Registers the CA cert when provided.' do
71+
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_container_image, docker_pull_creds: creds_b64, base_64_ca_cert: cert_b64, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
72+
73+
cert_file = File.join(@certs_dir, hostname, 'ca.crt')
74+
expect(File.exist?(cert_file)).to be(true)
75+
expect(File.read(cert_file)).to eq(cert_txt)
76+
end
77+
end
78+
end
79+
80+
describe 'cd4pe_job_helper::get_docker_run_cmd' do
81+
it 'Generates the correct podman run command.' do
82+
test_manifest_type = "AFTER_JOB_SUCCESS"
83+
test_container_image = 'puppetlabs/test:10.0.1'
84+
arg1 = '--testarg=woot'
85+
arg2 = '--otherarg=hello'
86+
arg3 = '--whatever=doesntmatter'
87+
user_specified_container_run_args = [arg1, arg2, arg3]
88+
job_type = 'unix'
89+
90+
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_container_image, docker_run_args: user_specified_container_run_args, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
91+
92+
podman_run_command = job_helper.get_docker_run_cmd(test_manifest_type)
93+
cmd_parts = podman_run_command.split(' ')
94+
95+
expect(cmd_parts[0]).to eq('podman')
96+
expect(cmd_parts[1]).to eq('run')
97+
expect(cmd_parts[2]).to eq('--rm')
98+
expect(cmd_parts[3]).to eq(arg1)
99+
expect(cmd_parts[4]).to eq(arg2)
100+
expect(cmd_parts[5]).to eq(arg3)
101+
expect(cmd_parts[6]).to eq('-e')
102+
expect(cmd_parts[7]).to eq('secret1')
103+
expect(cmd_parts[8]).to eq('-e')
104+
expect(cmd_parts[9]).to eq('secret2')
105+
expect(cmd_parts[10]).to eq('-v')
106+
expect(cmd_parts[11].end_with?("/#{File.basename(@working_dir)}/cd4pe_job/repo:/repo:z\"")).to be(true)
107+
expect(cmd_parts[12]).to eq('-v')
108+
expect(cmd_parts[13].end_with?("/#{File.basename(@working_dir)}/cd4pe_job/jobs/#{job_type}:/cd4pe_job:z\"")).to be(true)
109+
expect(cmd_parts[14]).to eq(test_container_image)
110+
expect(cmd_parts[15]).to eq('"/cd4pe_job/AFTER_JOB_SUCCESS"')
111+
end
112+
end
113+
end

spec/run_cd4pe_job_spec.rb

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -157,75 +157,6 @@
157157
expect(ENV['REPO_DIR']).to eq("#{@working_dir}/cd4pe_job/repo")
158158
end
159159
end
160-
161-
describe 'cd4pe_job_helper::update_docker_image' do
162-
let(:test_docker_image) { 'puppetlabs/test:10.0.1' }
163-
it 'Generates a docker pull command.' do
164-
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_docker_image, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
165-
docker_pull_command = job_helper.get_docker_pull_cmd
166-
expect(docker_pull_command).to eq("docker pull #{test_docker_image}")
167-
end
168-
169-
context 'with config' do
170-
let(:hostname) { 'host1' }
171-
let(:creds_json) { {auths: {hostname => {}}}.to_json }
172-
let(:creds_b64) { Base64.encode64(creds_json) }
173-
let(:cert_txt) { 'junk' }
174-
let(:cert_b64) { Base64.encode64(cert_txt) }
175-
176-
it 'Uses config when present.' do
177-
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_docker_image, docker_pull_creds: creds_b64, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
178-
config_json = File.join(@working_dir, '.docker', 'config.json')
179-
expect(File.exist?(config_json)).to be(true)
180-
expect(File.read(config_json)).to eq(creds_json)
181-
182-
docker_pull_command = job_helper.get_docker_pull_cmd
183-
expect(docker_pull_command).to eq("docker --config #{File.join(@working_dir, '.docker')} pull #{test_docker_image}")
184-
end
185-
186-
it 'Registers the CA cert when provided.' do
187-
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_docker_image, docker_pull_creds: creds_b64, base_64_ca_cert: cert_b64, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
188-
189-
cert_file = File.join(@certs_dir, hostname, 'ca.crt')
190-
expect(File.exist?(cert_file)).to be(true)
191-
expect(File.read(cert_file)).to eq(cert_txt)
192-
end
193-
end
194-
end
195-
196-
describe 'cd4pe_job_helper::get_docker_run_cmd' do
197-
it 'Generates the correct docker run command.' do
198-
test_manifest_type = "AFTER_JOB_SUCCESS"
199-
test_docker_image = 'puppetlabs/test:10.0.1'
200-
arg1 = '--testarg=woot'
201-
arg2 = '--otherarg=hello'
202-
arg3 = '--whatever=doesntmatter'
203-
user_specified_docker_run_args = [arg1, arg2, arg3]
204-
job_type = @windows_job ? 'windows' : 'unix'
205-
206-
job_helper = CD4PEJobRunner.new(windows_job: @windows_job, working_dir: @working_dir, docker_image: test_docker_image, docker_run_args: user_specified_docker_run_args, job_token: @job_token, web_ui_endpoint: @web_ui_endpoint, job_owner: @job_owner, job_instance_id: @job_instance_id, logger: @logger, secrets: @secrets)
207-
208-
docker_run_command = job_helper.get_docker_run_cmd(test_manifest_type)
209-
cmd_parts = docker_run_command.split(' ')
210-
211-
expect(cmd_parts[0]).to eq('docker')
212-
expect(cmd_parts[1]).to eq('run')
213-
expect(cmd_parts[2]).to eq('--rm')
214-
expect(cmd_parts[3]).to eq(arg1)
215-
expect(cmd_parts[4]).to eq(arg2)
216-
expect(cmd_parts[5]).to eq(arg3)
217-
expect(cmd_parts[6]).to eq('-e')
218-
expect(cmd_parts[7]).to eq('secret1')
219-
expect(cmd_parts[8]).to eq('-e')
220-
expect(cmd_parts[9]).to eq('secret2')
221-
expect(cmd_parts[10]).to eq('-v')
222-
expect(cmd_parts[11].end_with?("/#{File.basename(@working_dir)}/cd4pe_job/repo:/repo\"")).to be(true)
223-
expect(cmd_parts[12]).to eq('-v')
224-
expect(cmd_parts[13].end_with?("/#{File.basename(@working_dir)}/cd4pe_job/jobs/#{job_type}:/cd4pe_job\"")).to be(true)
225-
expect(cmd_parts[14]).to eq(test_docker_image)
226-
expect(cmd_parts[15]).to eq('"/cd4pe_job/AFTER_JOB_SUCCESS"')
227-
end
228-
end
229160
end
230161

231162
describe 'cd4pe_job_helper::run_job' do

tasks/run_cd4pe_job.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@
2424
},
2525
"docker_image": {
2626
"type": "Optional[String[1]]",
27-
"description": "If specified, the job will attempt to run inside the docker container."
27+
"description": "If specified, the job will attempt to run inside a container."
2828
},
2929
"docker_run_args": {
3030
"type": "Optional[Array[String[1]]]",
31-
"description": "If specified, the arguements will be passed to docker if docker image is specified."
31+
"description": "The arguments to pass to the container runtime."
3232
},
3333
"docker_pull_creds": {
3434
"type": "Optional[String[1]]",
35-
"description": "Base64-encoded docker config.json to use when pulling the specified docker image.",
35+
"description": "Base64-encoded config.json to use when pulling the specified container image.",
3636
"sensitive": true
3737
},
3838
"base_64_ca_cert": {

0 commit comments

Comments
 (0)