Class: OsCtld::Container::DatasetBuilder

Inherits:
Object
  • Object
show all
Includes:
OsCtl::Lib::Utils::Log, OsCtl::Lib::Utils::System, Utils::SwitchUser
Defined in:
lib/osctld/container/dataset_builder.rb

Instance Method Summary collapse

Methods included from Utils::SwitchUser

#ct_attach, #ct_syscmd

Constructor Details

#initialize(opts = {}) ⇒ DatasetBuilder

Returns a new instance of DatasetBuilder.

Parameters:

  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :cmd (Command::Base)


12
13
14
# File 'lib/osctld/container/dataset_builder.rb', line 12

def initialize(opts = {})
  @builder_opts = opts
end

Instance Method Details

#copy_datasets(src, dst, from: nil) ⇒ String

Returns snapshot name.

Parameters:

  • src (Array<OsCtl::Lib::Zfs::Dataset>)
  • dst (Array<OsCtl::Lib::Zfs::Dataset>)
  • from (String, nil) (defaults to: nil)

    base snapshot

Returns:

  • (String)

    snapshot name



46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/osctld/container/dataset_builder.rb', line 46

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

Parameters:

  • ds (OsCtl::Lib::Zfs::Dataset)
  • opts (Hash) (defaults to: {})

    options

Options Hash (opts):

  • :uid_map (OsCtl::Lib::IdMap)
  • :gid_map (OsCtl::Lib::IdMap)
  • :parents (Boolean)
  • :properties (Hash)


22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/osctld/container/dataset_builder.rb', line 22

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

Parameters:

  • image (String)

    image path

  • dir (String)

    dir to extract it to

  • ds (OsCtl::Lib::Zfs::Dataset)
  • opts (Hash) (defaults to: {})

    options

Options Hash (opts):

  • :distribution (String)
  • :version (String)
  • :mapping (Boolean)


68
69
70
71
72
# File 'lib/osctld/container/dataset_builder.rb', line 68

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

Parameters:

  • image (String)

    image path

  • member (String)

    file from the tar to use

  • compression (:gzip, :off)

    compression type

  • ds (OsCtl::Lib::Zfs::Dataset)


78
79
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
# File 'lib/osctld/container/dataset_builder.rb', line 78

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| c.join(' ') }.join(' | ')

  # 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.
  pid = Process.spawn(command_string)
  Process.wait(pid)

  if $?.exitstatus != 0
    raise "failed to import stream: command '#{command_string}' " \
          "exited with #{$?.exitstatus}"
  end

  nil
end

#progress(msg) ⇒ Object (protected)



159
160
161
162
163
# File 'lib/osctld/container/dataset_builder.rb', line 159

def progress(msg)
  return unless @builder_opts[:cmd]

  @builder_opts[:cmd].send(:progress, msg)
end

#shift_dataset(ds, opts = {}) ⇒ Object

Parameters:

  • ds (OsCtl::Lib::Zfs::Dataset)
  • opts (Hash) (defaults to: {})

    options

Options Hash (opts):

  • :uid_map (OsCtl::Lib::IdMap)
  • :gid_map (OsCtl::Lib::IdMap)


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
# File 'lib/osctld/container/dataset_builder.rb', line 116

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)

    if (opts[:uid_map] && st.uid == opts[:uid_map].ns_to_host(0)) \
       || (opts[:gid_map] && st.gid == opts[:gid_map].ns_to_host(0))
      return # rubocop:disable Lint/NonLocalExitFromIterator
    end

    zfs(:unmount, nil, ds)
    sleep(1 + i)
  end

  raise 'unable to configure UID/GID mapping'
end