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



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

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)


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

def create_dataset(ds, opts = {})
  zfs_opts = {
    properties: {
      canmount: 'noauto'
    }
  }
  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)


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

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)


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

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)



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

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)


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

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