Class: OsCtld::Container::Importer

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

Overview

An interface for reading tar archives generated by OsCtl::Lib::Container::Exporter

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pool, io) ⇒ Importer

Returns a new instance of Importer



14
15
16
17
# File 'lib/osctld/container/importer.rb', line 14

def initialize(pool, io)
  @pool = pool
  @tar = Gem::Package::TarReader.new(io)
end

Instance Attribute Details

#metadataObject (readonly, protected)

Returns the value of attribute metadata



248
249
250
# File 'lib/osctld/container/importer.rb', line 248

def 
  @metadata
end

#poolObject (readonly, protected)

Returns the value of attribute pool



248
249
250
# File 'lib/osctld/container/importer.rb', line 248

def pool
  @pool
end

#tarObject (readonly, protected)

Returns the value of attribute tar



248
249
250
# File 'lib/osctld/container/importer.rb', line 248

def tar
  @tar
end

Instance Method Details

#closeObject



243
244
245
# File 'lib/osctld/container/importer.rb', line 243

def close
  tar.close
end

#copy_file_to_disk(entry, dst) ⇒ Object (protected)

Copy file from the tar archive to disk

Parameters:

  • entry (Gem::Package::TarReader::Entry)
  • dst (String)


301
302
303
304
305
# File 'lib/osctld/container/importer.rb', line 301

def copy_file_to_disk(entry, dst)
  File.open(dst, 'w', entry.header.mode & 07777) do |df|
    df.write(entry.read(16*1024)) until entry.eof?
  end
end

#create_datasets(builder) ⇒ Object

Create the root and all descendants datasets

Parameters:



172
173
174
175
176
# File 'lib/osctld/container/importer.rb', line 172

def create_datasets(builder)
  each_dataset(builder) do |ds|
    builder.create_dataset(ds, mapping: true, parents: ds.root?)
  end
end

#ct_idObject



40
41
42
# File 'lib/osctld/container/importer.rb', line 40

def ct_id
  ['container']
end

#each_dataset(builder) {|ds| ... } ⇒ Object

Iterate over all container datasets

Parameters:

Yield Parameters:

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


230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/osctld/container/importer.rb', line 230

def each_dataset(builder, &block)
  block.call(builder.ct.dataset)

  @datasets ||= ['datasets'].map do |name|
    OsCtl::Lib::Zfs::Dataset.new(
      File.join(builder.ct.dataset.name, name),
      base: builder.ct.dataset.name
    )
  end

  @datasets.each(&block)
end

#get_or_create_groupObject

Load the group from the archive and register it

If a group with the same name already exists and all its parameters are the same, the existing group is returned. Otherwise an exception is raised.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/osctld/container/importer.rb', line 136

def get_or_create_group
  name = ['group']

  db = DB::Groups.find(name, pool)
  grp = load_group

  if db.nil?
    # The group does not exist, create it
    Commands::Group::Create.run!(
      pool: pool.name,
      name: grp.name
    )

    return DB::Groups.find(name, pool) || (fail 'expected group')
  end

  db
end

#get_or_create_userObject

Load the user from the archive and register him

If a user with the same name already exists and all his parameters are the same, the existing user is returned. Otherwise an exception is raised.



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

def get_or_create_user
  name = ['user']

  db = DB::Users.find(name, pool)
  u = load_user

  if db.nil?
    # The user does not exist, create him
    Commands::User::Create.run!(
      pool: pool.name,
      name: u.name,
      ugid: u.ugid,
      uid_map: u.uid_map.export,
      gid_map: u.gid_map.export,
    )

    return DB::Users.find(name, pool) || (fail 'expected user')
  end

  %i(ugid uid_map gid_map).each do |param|
    mine = db.send(param)
    other = u.send(param)
    next if mine == other

    fail "user #{pool.name}:#{name} already exists: #{param} mismatch: "+
         "existing #{mine}, trying to import #{other}"
  end

  db
end

#group_nameObject



36
37
38
# File 'lib/osctld/container/importer.rb', line 36

def group_name
  ['group']
end

#install_user_hook_scripts(ct) ⇒ Object

Load user-defined script hooks from the archive and install them

Parameters:



157
158
159
160
161
162
163
164
165
166
167
# File 'lib/osctld/container/importer.rb', line 157

def install_user_hook_scripts(ct)
  tar.each do |entry|
    next if !entry.full_name.start_with?('hooks/') || !entry.file? \
            || entry.full_name.count('/') > 1

    name = File.basename(entry.full_name)
    next unless Container::Hook.exist?(name.gsub(/-/, '_').to_sym)

    copy_file_to_disk(entry, File.join(ct.user_hook_script_dir, name))
  end
end

#load_ct(opts) ⇒ Object

Create a new instance of OsCtld::Container as described by the tar archive

The returned CT is not registered in the internal database, it may even conflict with a CT already registered in the database.

Parameters:

  • opts (Hash)

    options

Options Hash (opts):



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/osctld/container/importer.rb', line 80

def load_ct(opts)
  id = opts[:id] || ['container']
  user = opts[:user] || get_or_create_user
  group = opts[:group] || get_or_create_group
  ct_opts = opts[:ct_opts] || {}
  ct_opts[:load_from] = tar.seek('config/container.yml') { |entry| entry.read }

  Container.new(
    pool,
    id,
    user,
    group,
    opts[:dataset] || Container.default_dataset(pool, id),
    ct_opts
  )
end

#load_groupObject

Create a new instance of Group as described by the tar archive

The returned group is not registered in the internal database, it may even conflict with a group already registered in the database.



60
61
62
63
64
65
66
67
# File 'lib/osctld/container/importer.rb', line 60

def load_group
  Group.new(
    pool,
    ['group'],
    config: tar.seek('config/group.yml') { |entry| entry.read },
    devices: false
  )
end

#load_metadataObject

Load metadata describing the archive

Loading the metadata is the first thing that should be done, because all other methods depend on its result.



23
24
25
26
27
28
29
30
# File 'lib/osctld/container/importer.rb', line 23

def 
  ret = tar.seek('metadata.yml') do |entry|
    YAML.load(entry.read)
  end
  fail 'metadata.yml not found' unless ret
  @metadata = ret
  ret
end

#load_rootfs(builder) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/osctld/container/importer.rb', line 178

def load_rootfs(builder)
  case ['format']
  when 'zfs'
    load_streams(builder)

  when 'tar'
    unpack_rootfs(builder)

  else
    fail "unsupported archive format '#{['format']}'"
  end
end

#load_stream(builder, ds, name, required) ⇒ Object (protected)



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/osctld/container/importer.rb', line 250

def load_stream(builder, ds, name, required)
  found = nil

  stream_names(name).each do |file, compression|
    tf = tar.find { |entry| entry.full_name == file }

    if tf.nil?
      tar.rewind
      next
    end

    found = [tf, compression]
    break
  end

  if found.nil?
    tar.rewind
    fail "unable to import: #{name} not found" if required
    return
  end

  entry, compression = found
  process_stream(builder, ds, entry, compression)
  tar.rewind
end

#load_streams(builder) ⇒ Object

Load ZFS data streams from the archive and write them to appropriate datasets

Parameters:



195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/osctld/container/importer.rb', line 195

def load_streams(builder)
  each_dataset(builder) do |ds|
    load_stream(builder, ds, File.join(ds.relative_name, 'base'), true)
    load_stream(builder, ds, File.join(ds.relative_name, 'incremental'), false)
  end

  tar.seek('snapshots.yml') do |entry|
    snapshots = YAML.load(entry.read)

    each_dataset(builder) do |ds|
      snapshots.each { |snap| zfs(:destroy, nil, "#{ds}@#{snap}") }
    end
  end
end

#load_userObject

Create a new instance of User as described by the tar archive

The returned user is not registered in the internal database, it may even conflict with a user already registered in the database.



48
49
50
51
52
53
54
# File 'lib/osctld/container/importer.rb', line 48

def load_user
  User.new(
    pool,
    ['user'],
    config: tar.seek('config/user.yml') { |entry| entry.read }
  )
end

#process_stream(builder, ds, tf, compression) ⇒ Object (protected)



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/osctld/container/importer.rb', line 276

def process_stream(builder, ds, tf, compression)
  builder.from_stream(ds) do |recv|
    case compression
    when :gzip
      gz = Zlib::GzipReader.new(tf)
      recv.write(gz.readpartial(16*1024)) until gz.eof?
      gz.close

    when :off
      recv.write(tf.read(16*1024)) until tf.eof?

    else
      fail "unexpected compression type '#{compression}'"
    end
  end
end

#stream_names(name) ⇒ Object (protected)



293
294
295
296
# File 'lib/osctld/container/importer.rb', line 293

def stream_names(name)
  base = File.join('rootfs', "#{name}.dat")
  [[base, :off], ["#{base}.gz", :gzip]]
end

#unpack_rootfs(builder) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/osctld/container/importer.rb', line 210

def unpack_rootfs(builder)
  # Create private/
  builder.setup_rootfs

  ret = tar.seek('rootfs/base.tar.gz') do |tf|
    IO.popen("exec tar -xz -C #{builder.ct.rootfs}", 'r+') do |io|
      io.write(tf.read(16*1024)) until tf.eof?
    end

    fail "tar failed with exit status #{$?.exitstatus}" if $?.exitstatus != 0
    true
  end

  fail 'rootfs archive not found' unless ret === true
end

#user_nameObject



32
33
34
# File 'lib/osctld/container/importer.rb', line 32

def user_name
  ['user']
end