Class: Registration::SwMgmt

Inherits:
Object
  • Object
show all
Extended by:
Yast::I18n
Includes:
Yast, Yast::Logger
Defined in:
src/lib/registration/sw_mgmt.rb

Constant Summary collapse

ZYPP_DIR =
"/etc/zypp".freeze
FAKE_BASE_PRODUCT =
{ "name" => "SLES", "arch" => "x86_64", "version" => "12-0",
"flavor" => "DVD", "version_version" => "12", "register_release" => "",
"register_target" => "sle-12-x86_64" }.freeze
OEM_DIR =
"/var/lib/suseRegister/OEM".freeze

Class Method Summary collapse

Class Method Details

.add_service(product_service, credentials) ⇒ Object

add the services to libzypp and load (refresh) them


215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
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
275
276
277
278
279
280
281
282
283
284
285
# File 'src/lib/registration/sw_mgmt.rb', line 215

def self.add_service(product_service, credentials)
  # save repositories before refreshing added services (otherwise
  # pkg-bindings will treat them as removed by the service refresh and
  # unload them)
  if !Pkg.SourceSaveAll
    # error message
    raise ::Registration::PkgError, N_("Saving repository configuration failed.")
  end

  # services for registered products
  log.info "Adding service #{product_service.name.inspect} (#{product_service.url})"

  credentials_file = UrlHelpers.credentials_from_url(product_service.url)

  if credentials_file
    if Mode.update
      # at update libzypp is already switched to /mnt target,
      # update the path accordingly
      credentials_file = File.join(Installation.destdir,
        ::SUSE::Connect::YaST::DEFAULT_CREDENTIALS_DIR,
        credentials_file)
      log.info "Using #{credentials_file} credentials path in update mode"
    end
    # SCC uses the same credentials for all services, just save them to
    # a different file
    SUSE::Connect::YaST.create_credentials_file(credentials.username,
      credentials.password, credentials_file)
  end

  service_name = product_service.name

  # add a new service or update the existing service
  if Pkg.ServiceAliases.include?(service_name)
    log.info "Updating existing service: #{service_name}"
    if !Pkg.ServiceSet(service_name,
      "alias"       => service_name,
      "name"        => service_name,
      "url"         => product_service.url.to_s,
      "enabled"     => true,
      "autorefresh" => true)

      ## error message
      raise ::Registration::ServiceError.new(N_("Updating service '%s' failed."), service_name)
    end
  else
    log.info "Adding new service: #{service_name}"
    if !Pkg.ServiceAdd(service_name, product_service.url.to_s)
      # error message
      raise ::Registration::ServiceError.new(N_("Adding service '%s' failed."), service_name)
    end

    if !Pkg.ServiceSet(service_name, "autorefresh" => true)
      # error message
      raise ::Registration::ServiceError.new(N_("Updating service '%s' failed."), service_name)
    end
  end

  # refresh works only for saved services
  if !Pkg.ServiceSave(service_name)
    # error message
    raise ::Registration::ServiceError.new(N_("Saving service '%s' failed."), service_name)
  end

  # Force refreshing due timing issues (bnc#967828)
  if !Pkg.ServiceForceRefresh(service_name)
    # error message
    raise ::Registration::ServiceError.new(N_("Refreshing service '%s' failed."), service_name)
  end
ensure
  Pkg.SourceSaveAll
end

.base_product_to_registerObject


197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'src/lib/registration/sw_mgmt.rb', line 197

def self.base_product_to_register
  # use FAKE_BASE_PRODUCT just for debugging
  base_product = ENV["FAKE_BASE_PRODUCT"] ? FAKE_BASE_PRODUCT : find_base_product

  # filter out not needed data
  product_info = {
    "name"         => base_product["name"],
    "arch"         => base_product["arch"],
    "version"      => base_product["version_version"],
    "release_type" => get_release_type(base_product)
  }

  log.info("Base product to register: #{product_info}")

  product_info
end

.check_repositoriesBoolean

try refreshing all enabled repositories with autorefresh enabled and report repositories which fail, ask the user to disable them or to abort


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
# File 'src/lib/registration/sw_mgmt.rb', line 84

def self.check_repositories
  # only enabled repositories
  repos = Pkg.SourceGetCurrent(true)

  repos.each do |repo|
    data = Pkg.SourceGeneralData(repo)
    # skip repositories which have autorefresh disabled
    next unless data["autorefresh"]

    log.info "Refreshing repository #{data["alias"].inspect}"
    next if Pkg.SourceRefreshNow(repo)

    # TRANSLATORS: error popup, %s is a repository name, the popup is displayed
    # when a migration repository cannot be accessed, there are [Skip]
    # and [Abort] buttons displayed below the question
    question = _("Repository '%s'\ncannot be loaded.\n\n"\
        "Skip the repository or abort?") % data["name"]
    ret = Popup.ErrorAnyQuestion(Label.ErrorMsg, question, Label.SkipButton,
      Label.AbortButton, :focus_yes)

    log.info "Abort online migration: #{ret}"
    return false unless ret

    # disable the repository
    log.info "Disabling repository #{data["alias"].inspect}"
    Pkg.SourceSetEnabled(repo, false)

    # make sure the repository is enabled again after migration
    RepoStateStorage.instance.add(repo, true)
  end

  true
end

.copy_old_credentials(source_dir) ⇒ Object

copy old NCC/SCC credentials from the old installation to new SCC credentials the files are copied to the root of the current system (/), at installation the credentials are copied to the target (/mnt) at the beginning of the installation (in the inst_kickoff.rb client)


355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'src/lib/registration/sw_mgmt.rb', line 355

def self.copy_old_credentials(source_dir)
  log.info "Searching registration credentials in #{source_dir}..."

  # ensure the zypp directory is writable in inst-sys
  zypp_config_writable!

  dir = SUSE::Connect::YaST::DEFAULT_CREDENTIALS_DIR
  # create the target directory if missing
  if !File.exist?(dir)
    log.info "Creating directory #{dir}"
    ::FileUtils.mkdir_p(dir)
  end

  # check for NCC credentials
  ncc_file = File.join(source_dir, dir, "NCCcredentials")
  copy_old_credentials_file(ncc_file)

  scc_file = File.join(source_dir, SUSE::Connect::YaST::GLOBAL_CREDENTIALS_FILE)
  copy_old_credentials_file(scc_file)
end

.find_addon_updates(addons) ⇒ Object


394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'src/lib/registration/sw_mgmt.rb', line 394

def self.find_addon_updates(addons)
  log.info "Available addons: #{addons.map(&:identifier)}"

  products = Pkg.ResolvableProperties("", :product, "")

  installed_addons = products.select do |product|
    product["status"] == :installed && product["type"] != "base"
  end

  product_names = installed_addons.map { |a| "#{a["name"]}-#{a["version"]}-#{a["release"]}" }
  log.info "Installed addons: #{product_names}"

  ret = addons.select do |addon|
    installed_addons.any? do |installed_addon|
      addon.updates_addon?(installed_addon)
    end
  end

  log.info "Found addons to update: #{ret.map(&:identifier)}"
  ret
end

.find_base_productObject


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
# File 'src/lib/registration/sw_mgmt.rb', line 137

def self.find_base_product
  # just for debugging:
  return FAKE_BASE_PRODUCT if ENV["FAKE_BASE_PRODUCT"]

  # during installation the products are :selected,
  # on a running system the products are :installed
  # during upgrade use the newer selected product (same as in installation)
  products = Pkg.ResolvableProperties("", :product, "").find_all do |p|
    if Stage.initial
      # during installation the type is not valid yet yet
      # (the base product is determined by /etc/products.d/baseproduct symlink)
      # the base product comes from the first repository
      p["source"] == 0
    else
      # in installed system the base product has valid type
      p["status"] == :installed && p["type"] == "base"
    end
  end

  log.debug "Found base products: #{products}"
  log.info "Found base products: #{products.map { |p| p["name"] }}"
  log.warn "More than one base product found!" if products.size > 1

  products.first
end

.get_release_type(product) ⇒ Object


510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'src/lib/registration/sw_mgmt.rb', line 510

def self.get_release_type(product)
  if product["product_line"]
    oem_file = File.join(OEM_DIR, product["product_line"])

    if File.exist?(oem_file)
      # read only the first line
      line = File.open(oem_file, &:readline)
      return line.chomp if line
    end
  end

  product["register_release"]
end

.init(load_packages = false) ⇒ Object

initialize the package management


66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'src/lib/registration/sw_mgmt.rb', line 66

def self.init(load_packages = false)
  # false = do not allow continuing without the libzypp lock
  lock = PackageLock.Connect(false)
  raise_pkg_exception unless lock["connected"]

  # display progress when refreshing repositories
  PackageCallbacks.InitPackageCallbacks

  raise_pkg_exception unless Pkg.TargetInitialize(Installation.destdir)
  raise_pkg_exception unless Pkg.TargetLoad
  raise_pkg_exception unless Pkg.SourceRestore

  raise_pkg_exception if load_packages && !Pkg.SourceLoad
end

.installed_productsObject


163
164
165
166
167
168
169
170
171
172
173
# File 'src/lib/registration/sw_mgmt.rb', line 163

def self.installed_products
  # just for testing/debugging
  return [FAKE_BASE_PRODUCT] if ENV["FAKE_BASE_PRODUCT"]

  products = Pkg.ResolvableProperties("", :product, "").select do |p|
    p["status"] == :installed
  end

  log.info "Found installed products: #{products.map { |p| p["name"] }}"
  products
end

.product_label(base_product) ⇒ String

create UI label for a base product


190
191
192
193
194
195
# File 'src/lib/registration/sw_mgmt.rb', line 190

def self.product_label(base_product)
  base_product["display_name"] ||
    base_product["short_name"] ||
    base_product["name"] ||
    _("Unknown product")
end

.products_from_repo(repo_id) ⇒ Object

find the product resolvables from the specified repository


503
504
505
506
507
508
# File 'src/lib/registration/sw_mgmt.rb', line 503

def self.products_from_repo(repo_id)
  # TODO: only installed products??
  Pkg.ResolvableProperties("", :product, "").select do |product|
    product["source"] == repo_id
  end
end

.raise_pkg_exceptionObject

Raises:


524
525
526
# File 'src/lib/registration/sw_mgmt.rb', line 524

def self.raise_pkg_exception
  raise PkgError.new, Pkg.LastError
end

.remote_product(product) ⇒ SUSE::Connect::Remote::Product

convert a libzypp Product Hash to a SUSE::Connect::Remote::Product object


178
179
180
181
182
183
184
185
# File 'src/lib/registration/sw_mgmt.rb', line 178

def self.remote_product(product)
  OpenStruct.new(
    arch:         product["arch"],
    identifier:   product["name"],
    version:      product["version"],
    release_type: product["release_type"]
  )
end

.remove_service(name) ⇒ Object

remove a libzypp service and save the repository configuration


289
290
291
292
293
294
295
296
# File 'src/lib/registration/sw_mgmt.rb', line 289

def self.remove_service(name)
  log.info "Removing service #{name}"

  if Pkg.ServiceDelete(name) && !Pkg.SourceSaveAll
    # error message
    raise ::Registration::PkgError, N_("Saving repository configuration failed.")
  end
end

.repository_data(repo) ⇒ Hash

get repository data


324
325
326
327
328
# File 'src/lib/registration/sw_mgmt.rb', line 324

def self.repository_data(repo)
  data = Pkg.SourceGeneralData(repo)
  data["SrcId"] = repo
  data
end

.select_addon_productsBoolean

select products for new added extensions/modules


445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'src/lib/registration/sw_mgmt.rb', line 445

def self.select_addon_products
  addon_services = ::Registration::Storage::Cache.instance.addon_services
  log.info "New addon services: #{addon_services}"

  new_repos = addon_services.reduce([]) do |acc, service|
    acc.concat(::Registration::SwMgmt.service_repos(service))
  end

  return true if new_repos.empty?

  products = Pkg.ResolvableProperties("", :product, "")
  products.select! do |product|
    product["status"] == :available &&
      new_repos.any? { |new_repo| product["source"] == new_repo["SrcId"] }
  end
  products.map! { |product| product["name"] }

  log.info "Products to install: #{products}"

  ret = products.all? { |product| Pkg.ResolvableInstall(product, :product) }

  # preselect the default product patterns (FATE#320199)
  # note: must be called *after* selecting the products
  product_patterns = ProductPatterns.new
  log.info "Selecting the default product patterns: #{product_patterns.names}"
  product_patterns.select

  ret
end

.select_product_addons(products, addons) ⇒ Object

select remote addons matching the product resolvables


476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# File 'src/lib/registration/sw_mgmt.rb', line 476

def self.select_product_addons(products, addons)
  addons.each do |addon|
    log.info "Found remote addon: #{addon.identifier}-#{addon.version}-#{addon.arch}"
  end

  # select a remote addon for each product
  products.each do |product|
    remote_addon = addons.find do |addon|
      product["name"] == addon.identifier &&
        product["version_version"] == addon.version &&
        product["arch"] == addon.arch
    end

    if remote_addon
      remote_addon.selected
    else
      product_label = "#{product["display_name"]} (#{product["name"]}" \
        "-#{product["version_version"]}-#{product["arch"]})"

      # TRANSLATORS: %s is a product name
      Report.Error(_("Cannot find remote product %s.\n" \
            "The product cannot be registered.") % product_label)
    end
  end
end

.service_repos(product_service, only_updates: false) ⇒ Array<Hash>

get list of repositories belonging to registered services


302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'src/lib/registration/sw_mgmt.rb', line 302

def self.service_repos(product_service, only_updates: false)
  repo_data = Pkg.SourceGetCurrent(false).map { |repo| repository_data(repo) }

  service_name = product_service.name
  log.info "Service name: #{service_name.inspect}"

  # select only repositories belonging to the product services
  repos = repo_data.select { |repo| service_name == repo["service"] }
  log.info "Service repositories: #{repos}"

  if only_updates
    # leave only update repositories
    repos.select! { |repo| repo["is_update_repo"] }
    log.info "Found update repositories: #{repos}"
  end

  repos
end

.set_repos_state(repos, enabled) ⇒ void

This method returns an undefined value.

Set repository state (enabled/disabled) The original repository state is saved to RepoStateStorage to restore the original state later.


336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'src/lib/registration/sw_mgmt.rb', line 336

def self.set_repos_state(repos, enabled)
  # keep the defaults when not defined
  return if enabled.nil?

  repos.each do |repo|
    next if repo["enabled"] == enabled

    # remember the original state
    RepoStateStorage.instance.add(repo["SrcId"], repo["enabled"])

    log.info "Changing repository state: #{repo["name"]} enabled: #{enabled}"
    Pkg.SourceSetEnabled(repo["SrcId"], enabled)
  end
end

.update_product_renames(renames) ⇒ Object

update the static defaults in AddOnProduct module


417
418
419
420
421
# File 'src/lib/registration/sw_mgmt.rb', line 417

def self.update_product_renames(renames)
  renames.each do |old_name, new_name|
    AddOnProduct.add_rename(old_name, new_name)
  end
end

.zypp_config_writable!Object

during installation /etc/zypp directory is not writable (mounted on a read-only file system), the workaround is to copy the whole directory structure into a writable temporary directory and override the original location by "mount -o bind"


122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'src/lib/registration/sw_mgmt.rb', line 122

def self.zypp_config_writable!
  return if !(Mode.installation || Mode.update) || File.writable?(ZYPP_DIR)

  log.info "Copying libzypp config to a writable place"

  # create writable zypp directory structure in /tmp
  tmpdir = Dir.mktmpdir

  log.info "Copying #{ZYPP_DIR} to #{tmpdir} ..."
  ::FileUtils.cp_r ZYPP_DIR, tmpdir

  log.info "Mounting #{tmpdir} to #{ZYPP_DIR}"
  `mount -o bind #{tmpdir}/zypp #{ZYPP_DIR}`
end