Class: Kitchen::Docker::Container::Linux
- Inherits:
-
Kitchen::Docker::Container
- Object
- Kitchen::Docker::Container
- Kitchen::Docker::Container::Linux
- Includes:
- Helpers::DockerfileHelper
- Defined in:
- lib/kitchen/docker/container/linux.rb
Overview
A Linux container, reached over SSH.
The generated image installs and runs an SSH server, and Test Kitchen connects to a published port with a generated key. That is why the Dockerfile here is so much larger than the Windows one.
Constant Summary collapse
- MUTEX_FOR_SSH_KEYS =
Serializes SSH key generation across concurrently running instances, which share one key path.
Mutex.new
Constants included from Helpers::ContainerHelper
Helpers::ContainerHelper::COPIED_MARKER
Instance Method Summary collapse
-
#container_ssh_port(state) ⇒ Integer
protected
The port Test Kitchen should connect to for SSH.
-
#create(state) ⇒ void
Builds the image, runs the container, and publishes its SSH port.
-
#dockerfile ⇒ String
protected
Builds the Dockerfile for a Linux container.
-
#execute(command) ⇒ String
Runs a command in the container by uploading it as a shell script.
-
#generate_keys ⇒ void
protected
Generates the SSH keypair used to log into Linux containers.
-
#initialize(config) ⇒ Linux
constructor
A new instance of Linux.
-
#parse_container_ssh_port(output) ⇒ Integer
protected
Pulls the published port out of
docker portoutput.
Methods included from Helpers::DockerfileHelper
#almalinux_platform, #amazonlinux_platform, #arch_platform, #centosstream_platform, #debian_platform, #dockerfile_base_linux, #dockerfile_platform, #fedora_platform, #gentoo_paludis_platform, #gentoo_platform, #opensuse_platform, #photonos_platform, #rhel_platform, #rockylinux_platform
Methods inherited from Kitchen::Docker::Container
Methods included from Helpers::ImageHelper
#build_image, #image_exists?, #image_in_use?, #parse_image_id, #remove_image
Methods included from Helpers::ContainerHelper
#container_env_variables, #container_exec, #container_exists?, #container_ip_address, #container_running?, #copy_file_to_container, #create_dir_on_container, #dockerfile_path, #dockerfile_proxy_config, #dockerfile_template, #file_on_container?, #ip_address?, #parse_container_id, #proxy_env_vars, #remote_socket?, #remove_container, #replace_env_variables, #run_container, #socket_uri, #verify_file_copied
Methods included from Helpers::CliHelper
#build_copy_command, #build_env_variable_args, #build_exec_command, #build_powershell_command, #build_run_command, #config_to_options, #dev_null, #docker_command, #docker_shell_opts, #docker_sudo_opts, #run_command, #shell_escape
Methods included from Helpers::FileHelper
Constructor Details
#initialize(config) ⇒ Linux
Returns a new instance of Linux.
38 39 40 |
# File 'lib/kitchen/docker/container/linux.rb', line 38 def initialize(config) super end |
Instance Method Details
#container_ssh_port(state) ⇒ Integer (protected)
The port Test Kitchen should connect to for SSH.
On the internal Docker network the container is reached directly, so the unmapped port 22 is correct; otherwise Docker's published mapping is looked up.
159 160 161 162 163 164 165 166 |
# File 'lib/kitchen/docker/container/linux.rb', line 159 def container_ssh_port(state) return 22 if @config[:use_internal_docker_network] output = docker_command("port #{state[:container_id]} 22/tcp") parse_container_ssh_port(output) rescue => e raise ActionFailed, "Docker reports container has no ssh port mapped. #{e}" end |
#create(state) ⇒ void
This method returns an undefined value.
Builds the image, runs the container, and publishes its SSH port.
47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/kitchen/docker/container/linux.rb', line 47 def create(state) super debug("Creating Linux container") generate_keys state[:ssh_key] = @config[:private_key] state[:image_id] = build_image(state, dockerfile) unless state[:image_id] state[:container_id] = run_container(state, 22) unless state[:container_id] state[:hostname] = hostname(state) state[:port] = container_ssh_port(state) end |
#dockerfile ⇒ String (protected)
Builds the Dockerfile for a Linux container.
A configured dockerfile is used as-is after ERB rendering. Otherwise
one is generated: the base image, proxy settings, the platform's own
package setup, any provision_command entries, and the generated public
key appended to the login user's authorized_keys.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/kitchen/docker/container/linux.rb', line 176 def dockerfile return dockerfile_template if @config[:dockerfile] from = "FROM #{@config[:image]}" platform = dockerfile_platform username = @config[:username] public_key = IO.read(@config[:public_key]).strip homedir = username == "root" ? "/root" : "/home/#{username}" base = dockerfile_base_linux(username, homedir) custom = "" Array(@config[:provision_command]).each do |cmd| custom << "RUN #{cmd}\n" end ssh_key = "RUN echo #{Shellwords.escape(public_key)} >> #{homedir}/.ssh/authorized_keys" # Empty string to ensure the file ends with a newline. output = [from, dockerfile_proxy_config, platform, base, custom, ssh_key, ""].join("\n") debug("--- Start Dockerfile ---") debug(output.strip) debug("--- End Dockerfile ---") output end |
#execute(command) ⇒ String
Runs a command in the container by uploading it as a shell script.
The command is written to a temp file and executed with bash rather than passed on the command line, which keeps long converge scripts clear of argument-length and quoting limits.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/kitchen/docker/container/linux.rb', line 69 def execute(command) # Create temp script file and upload files to container debug("Executing command on Linux container (Platform: #{@config[:platform]})") filename = "docker-#{::SecureRandom.uuid}.sh" temp_file = "./.kitchen/temp/#{filename}" create_temp_file(temp_file, command) remote_path = @config[:temp_dir] debug("Creating directory #{remote_path} on container") create_dir_on_container(@config, remote_path) debug("Uploading temp file #{temp_file} to #{remote_path} on container") upload(temp_file, remote_path) # Replace any environment variables used in the path and execute script file debug("Executing temp script #{remote_path}/#{filename} on container") remote_path = replace_env_variables(@config, remote_path) container_exec(@config, "/bin/bash #{remote_path}/#{filename}") rescue => e raise "Failed to execute command on Linux container. #{e}" ensure # Removed here rather than after the upload, so that a failure part # way through does not leave the script behind in .kitchen/temp. if temp_file && ::File.exist?(temp_file) debug("Deleting temp file from local filesystem") ::File.delete(temp_file) end end |
#generate_keys ⇒ void (protected)
This method returns an undefined value.
Generates the SSH keypair used to log into Linux containers.
Guarded by MUTEX_FOR_SSH_KEYS because concurrent instances share one key path and would otherwise write the file while another reads it.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/kitchen/docker/container/linux.rb', line 107 def generate_keys MUTEX_FOR_SSH_KEYS.synchronize do if !File.exist?(@config[:public_key]) || !File.exist?(@config[:private_key]) private_key = OpenSSL::PKey::RSA.new(2048) blobbed_key = Base64.encode64(private_key.to_blob).gsub("\n", "") public_key = "ssh-rsa #{blobbed_key} kitchen_docker_key" File.open(@config[:private_key], "w") do |file| file.write(private_key) file.chmod(0600) end File.open(@config[:public_key], "w") do |file| file.write(public_key) file.chmod(0600) end end end end |
#parse_container_ssh_port(output) ⇒ Integer (protected)
Pulls the published port out of docker port output.
One line is printed per published binding, and the host part varies:
0.0.0.0:32768
[::]:32768
The port is taken from the end of the line rather than by splitting on ":" from the left, because an IPv6 host contains colons of its own -- splitting left-to-right returned "" for those, and "".to_i is 0, so a daemon publishing on IPv6 handed Test Kitchen port 0 to connect to.
140 141 142 143 144 145 146 147 148 |
# File 'lib/kitchen/docker/container/linux.rb', line 140 def parse_container_ssh_port(output) port = output.lines.filter_map { |line| line[/:(\d+)\s*\z/, 1] }.first if port.nil? raise ActionFailed, "Could not parse Docker port output for container SSH port: #{output.inspect}" end port.to_i end |