Class: OsCtld::Container::DatasetBuilder
- Inherits:
-
Object
- Object
- OsCtld::Container::DatasetBuilder
- Includes:
- OsCtl::Lib::Utils::Log, OsCtl::Lib::Utils::System, Utils::SwitchUser
- Defined in:
- lib/osctld/container/dataset_builder.rb
Instance Method Summary collapse
-
#copy_datasets(src, dst, from: nil) ⇒ String
Snapshot name.
- #create_dataset(ds, opts = {}) ⇒ Object
- #from_local_archive(image, dir, ds, opts = {}) ⇒ Object
- #from_tar_stream(image, member, compression, ds) ⇒ Object
-
#initialize(opts = {}) ⇒ DatasetBuilder
constructor
A new instance of DatasetBuilder.
- #progress(msg) ⇒ Object protected
- #shift_dataset(ds, opts = {}) ⇒ Object
Methods included from Utils::SwitchUser
Constructor Details
#initialize(opts = {}) ⇒ DatasetBuilder
Returns a new instance of DatasetBuilder.
14 15 16 |
# File 'lib/osctld/container/dataset_builder.rb', line 14 def initialize(opts = {}) @builder_opts = opts end |
Instance Method Details
#copy_datasets(src, dst, from: nil) ⇒ String
Returns snapshot name.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/osctld/container/dataset_builder.rb', line 48 def copy_datasets(src, dst, from: nil) snap = "osctl-copy-#{from ? 'incr' : 'base'}-#{Time.now.to_i}" zfs(:snapshot, nil, src.map { |ds| "#{ds}@#{snap}" }.join(' ')) zipped = src.zip(dst) zipped.each do |src_ds, dst_ds| progress("Copying dataset #{src_ds.relative_name}") syscmd("zfs send -p -L #{from ? "-i @#{from}" : ''} #{src_ds}@#{snap} " \ "| zfs recv -F #{dst_ds}") end snap end |
#create_dataset(ds, opts = {}) ⇒ Object
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/osctld/container/dataset_builder.rb', line 24 def create_dataset(ds, opts = {}) zfs_opts = { properties: { canmount: 'noauto' }.merge(opts[:properties] || {}) } zfs_opts[:parents] = true if opts[:parents] if opts[:uid_map] zfs_opts[:properties][:uidmap] = opts[:uid_map].map(&:to_s).join(',') end if opts[:gid_map] zfs_opts[:properties][:gidmap] = opts[:gid_map].map(&:to_s).join(',') end ds.create!(**zfs_opts) ds.mount(recursive: true) end |
#from_local_archive(image, dir, ds, opts = {}) ⇒ Object
70 71 72 73 74 |
# File 'lib/osctld/container/dataset_builder.rb', line 70 def from_local_archive(image, dir, ds, opts = {}) progress('Extracting image') syscmd("tar -xzf #{image} -C #{dir}") shift_dataset(ds, opts) if opts[:mapping] end |
#from_tar_stream(image, member, compression, ds) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/osctld/container/dataset_builder.rb', line 80 def from_tar_stream(image, member, compression, ds) progress('Writing data stream') commands = [ ['tar', '-xOf', image, member] ] case compression when :gzip commands << ['gunzip'] when :off # no command else raise "unexpected compression type '#{compression}'" end commands << ['zfs', 'recv', '-F', ds.to_s] command_string = commands.map { |c| Shellwords.join(c) }.join(' | ') pipeline_script = <<~BASH #{command_string} pipeline_status=("${PIPESTATUS[@]}") printf '%s\n' "${pipeline_status[*]}" > "$1" for stage_status in "${pipeline_status[@]}"; do if (( stage_status != 0 )); then exit "$stage_status" fi done BASH # Note that we intentionally use shell to run the pipeline. Whenever ruby # is more involved in the process, we start to experience random deadlocks # when the zfs receive hangs. stderr_output, stage_statuses, status = Tempfile.create('osctld-import-stream-stderr') do |stderr| Tempfile.create('osctld-import-stream-status') do |status_file| pid = Process.spawn( 'bash', '-c', pipeline_script, 'osctld-import-stream', status_file.path, err: stderr ) wait_result = Process.wait2(pid) status_file.rewind statuses = status_file.read.strip.split.map { |v| Integer(v, 10) } stderr.rewind [stderr.read, statuses, wait_result[1]] end end complete_status = stage_statuses.length == commands.length return nil if status.success? && complete_status && stage_statuses.all?(&:zero?) shell_status = if status.signaled? "signal #{status.termsig}" else status.exitstatus.to_s end failed_stages = commands.each_with_index.filter_map do |command, i| stage_status = stage_statuses[i] next if stage_status.nil? || stage_status == 0 "stage #{i + 1} '#{Shellwords.join(command)}' exited with #{stage_status}" end detail = if !complete_status "pipeline reported #{stage_statuses.length} of #{commands.length} stage statuses; " \ "shell exited with #{shell_status}" elsif failed_stages.any? "#{failed_stages.join(', ')}; shell exited with #{shell_status}" else "shell exited with #{shell_status} despite successful stage statuses" end msg = "failed to import stream: #{detail}" stderr_output = stderr_output.strip stderr_tail = stderr_output.bytesize > 4096 ? stderr_output.byteslice(-4096, 4096) : stderr_output msg = "#{msg}, stderr: #{stderr_tail}" unless stderr_tail.empty? raise CommandFailed, msg end |
#progress(msg) ⇒ Object (protected)
214 215 216 217 218 |
# File 'lib/osctld/container/dataset_builder.rb', line 214 def progress(msg) return unless @builder_opts[:cmd] @builder_opts[:cmd].send(:progress, msg) end |
#shift_dataset(ds, opts = {}) ⇒ Object
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/osctld/container/dataset_builder.rb', line 169 def shift_dataset(ds, opts = {}) progress('Configuring UID/GID mapping') set_opts = [] if opts[:uid_map] set_opts << "\"uidmap=#{opts[:uid_map].map(&:to_s).join(',')}\"" end if opts[:gid_map] set_opts << "\"gidmap=#{opts[:gid_map].map(&:to_s).join(',')}\"" end if set_opts.empty? raise 'provide uid_map or gid_map' end zfs(:unmount, nil, ds, valid_rcs: [1]) zfs(:set, set_opts.join(' '), ds) 5.times do |i| zfs(:mount, nil, ds) f = Tempfile.create(['.ugid-map-test'], ds.mountpoint) f.close st = File.stat(f.path) File.unlink(f.path) uid_ok = !opts[:uid_map] || st.uid == opts[:uid_map].ns_to_host(0) gid_ok = !opts[:gid_map] || st.gid == opts[:gid_map].ns_to_host(0) if uid_ok && gid_ok return # rubocop:disable Lint/NonLocalExitFromIterator end zfs(:unmount, nil, ds) sleep(1 + i) end raise 'unable to configure UID/GID mapping' end |