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, _opts = {}) ⇒ Object

Parameters:

  • image (String)

    image path

  • dir (String)

    dir to extract it to

  • opts (Hash)

    options



65
66
67
68
69
# File 'lib/osctld/container/dataset_builder.rb', line 65

def from_local_archive(image, dir, _opts = {})
  progress('Extracting image')
  syscmd("tar -xzf #{image} -C #{dir}")
  shift_dataset
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)


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

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)



156
157
158
159
160
# File 'lib/osctld/container/dataset_builder.rb', line 156

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)


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

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