ssu
ssurepomanager.cpp
Go to the documentation of this file.
1 
9 /*
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
22  *
23  */
24 
25 #include <QStringList>
26 #include <QRegExp>
27 #include <QDirIterator>
28 
29 #include <zypp/RepoManager.h>
30 #include <zypp/RepoInfo.h>
31 
32 #include "sandbox_p.h"
33 #include "ssudeviceinfo.h"
34 #include "ssurepomanager.h"
35 #include "ssucoreconfig_p.h"
36 #include "ssusettings_p.h"
37 #include "ssulog_p.h"
38 #include "ssuvariables_p.h"
39 #include "ssufeaturemanager.h"
40 #include "ssu.h"
41 
42 #include "../constants.h"
43 
44 SsuRepoManager::SsuRepoManager()
45  : QObject()
46 {
47 }
48 
49 int SsuRepoManager::add(const QString &repo, const QString &repoUrl)
50 {
51  SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
52 
53  // adding a repo is a noop when device is in update mode...
54  if ((ssuSettings->deviceMode() & Ssu::UpdateMode) == Ssu::UpdateMode)
55  return -1;
56 
57  // ... or in AppInstallMode
58  if ((ssuSettings->deviceMode() & Ssu::AppInstallMode) == Ssu::AppInstallMode)
59  return -1;
60 
61  if (repoUrl.isEmpty()) {
62  // just enable a repository which has URL in repos.ini
63  QStringList enabledRepos;
64  if (ssuSettings->contains("enabled-repos"))
65  enabledRepos = ssuSettings->value("enabled-repos").toStringList();
66 
67  enabledRepos.append(repo);
68  enabledRepos.removeDuplicates();
69  ssuSettings->setValue("enabled-repos", enabledRepos);
70  } else {
71  ssuSettings->setValue("repository-urls/" + repo, repoUrl);
72  }
73 
74  ssuSettings->sync();
75  return 0;
76 }
77 
78 QString SsuRepoManager::caCertificatePath(const QString &domain)
79 {
80  SsuCoreConfig *settings = SsuCoreConfig::instance();
82 
83  QString ca = SsuVariables::variable(&repoSettings,
84  (domain.isEmpty() ? settings->domain() : domain) + "-domain",
85  "_ca-certificate").toString();
86  if (!ca.isEmpty())
87  return ca;
88 
89  // compat setting, can go away after some time
90  if (settings->contains("ca-certificate"))
91  return settings->value("ca-certificate").toString();
92 
93  return QString();
94 }
95 
96 int SsuRepoManager::disable(const QString &repo)
97 {
98  SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
99  QStringList disabledRepos;
100 
101  if (ssuSettings->contains("disabled-repos"))
102  disabledRepos = ssuSettings->value("disabled-repos").toStringList();
103 
104  disabledRepos.append(repo);
105  disabledRepos.removeDuplicates();
106 
107  ssuSettings->setValue("disabled-repos", disabledRepos);
108  ssuSettings->sync();
109 
110  return 0;
111 }
112 
113 int SsuRepoManager::enable(const QString &repo)
114 {
115  SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
116  QStringList disabledRepos;
117 
118  if (ssuSettings->contains("disabled-repos")) {
119  disabledRepos = ssuSettings->value("disabled-repos").toStringList();
120 
121  disabledRepos.removeAll(repo);
122  disabledRepos.removeDuplicates();
123 
124  if (disabledRepos.size() > 0)
125  ssuSettings->setValue("disabled-repos", disabledRepos);
126  else
127  ssuSettings->remove("disabled-repos");
128 
129  ssuSettings->sync();
130  }
131 
132 
133  return 0;
134 }
135 
136 int SsuRepoManager::remove(const QString &repo)
137 {
138  SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
139 
140  // removing a repo is a noop when device is in update mode...
141  if ((ssuSettings->deviceMode() & Ssu::UpdateMode) == Ssu::UpdateMode)
142  return -1;
143 
144  // ... or AppInstallMode
145  if ((ssuSettings->deviceMode() & Ssu::AppInstallMode) == Ssu::AppInstallMode)
146  return -1;
147 
148  if (ssuSettings->contains("repository-urls/" + repo))
149  ssuSettings->remove("repository-urls/" + repo);
150 
151  QStringList sections;
152  sections << "enabled-repos" << "disabled-repos";
153  for (const QString &section: sections) {
154  if (ssuSettings->contains(section)) {
155  QStringList repos = ssuSettings->value(section).toStringList();
156  repos.removeAll(repo);
157  repos.removeDuplicates();
158  if (repos.size() > 0)
159  ssuSettings->setValue(section, repos);
160  else
161  ssuSettings->remove(section);
162  }
163  }
164  ssuSettings->sync();
165 
166  return 0;
167 }
168 
169 QStringList SsuRepoManager::repos(int filter)
170 {
171  SsuDeviceInfo deviceInfo;
172  SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
173  bool rnd = false;
174 
175  if ((ssuSettings->deviceMode() & Ssu::RndMode) == Ssu::RndMode)
176  rnd = true;
177 
178  return repos(rnd, deviceInfo, filter);
179 }
180 
181 QStringList SsuRepoManager::repos(bool rnd, int filter)
182 {
183  SsuDeviceInfo deviceInfo;
184 
185  return repos(rnd, deviceInfo, filter);
186 }
187 
188 // @todo the non-device specific repository resolving should move from deviceInfo to repomanager
189 QStringList SsuRepoManager::repos(bool rnd, SsuDeviceInfo &deviceInfo, int filter)
190 {
191  QStringList result;
192 
193  // read the adaptation specific repositories, as well as the default
194  // repositories; default repos are read through deviceInfo as an
195  // adaptation is allowed to disable core repositories
196  result = deviceInfo.repos(rnd, filter);
197 
198  // read the repositories of the available features. While devices have
199  // a default list of features to be installed those are only relevant
200  // for bootstrapping, so this code just operates on installed features
201  SsuFeatureManager featureManager;
202  result.append(featureManager.repos(rnd, filter));
203 
204  // read user-defined repositories from ssu.ini. This step needs to
205  // happen at the end, after all other required repositories are
206  // added already
207 
208  // TODO: in strict mode, filter the repository list from there
209  SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
210 
211  bool updateMode = false;
212  bool appInstallMode = false;
213 
214  if ((ssuSettings->deviceMode() & Ssu::UpdateMode) == Ssu::UpdateMode)
215  updateMode = true;
216 
217  if ((ssuSettings->deviceMode() & Ssu::AppInstallMode) == Ssu::AppInstallMode) {
218  updateMode = true;
219  appInstallMode = true;
220  }
221 
222  if (filter == Ssu::NoFilter || filter == Ssu::UserFilter) {
223  // user defined repositories, or ones overriding URLs for default ones
224  // -> in update mode we need to check for each of those if it already
225  // exists. If it exists, keep it, if it does not, disable it
226  ssuSettings->beginGroup("repository-urls");
227  QStringList repoUrls = ssuSettings->allKeys();
228  ssuSettings->endGroup();
229 
230  if (updateMode) {
231  foreach (const QString &key, repoUrls) {
232  if (result.contains(key))
233  result.append(key);
234  }
235  } else {
236  result.append(repoUrls);
237  }
238 
239  // read user-enabled repositories from ssu.ini
240  if (ssuSettings->contains("enabled-repos") && !updateMode)
241  result.append(ssuSettings->value("enabled-repos").toStringList());
242 
243  // if the store repository is enabled keep it enabled in AppInstallMode
244  if (ssuSettings->contains("enabled-repos") && appInstallMode) {
245  // TODO store should not be hardcoded, but come via some store plugin
246  if (ssuSettings->value("enabled-repos").toStringList().contains("store"))
247  result.append("store");
248  }
249  }
250 
251  if (filter == Ssu::NoFilter ||
252  filter == Ssu::UserFilter ||
253  filter == Ssu::BoardFilterUserBlacklist) {
254  // read the disabled repositories from user configuration
255  if (ssuSettings->contains("disabled-repos") && !updateMode) {
256  foreach (const QString &key, ssuSettings->value("disabled-repos").toStringList())
257  result.removeAll(key);
258  }
259  }
260 
261 
262  result.sort();
263  result.removeDuplicates();
264 
265  return result;
266 }
267 
269 {
270  // - delete all non-ssu managed repositories (missing ssu_ prefix)
271  // - create list of ssu-repositories for current adaptation
272  // - go through ssu_* repositories, delete all which are not in the list; write others
273  SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
274  int deviceMode = ssuSettings->deviceMode();
275 
276  SsuLog *ssuLog = SsuLog::instance();
277 
278  if ((deviceMode & Ssu::DisableRepoManager) == Ssu::DisableRepoManager) {
279  ssuLog->print(LOG_INFO, "Repo management requested, but not enabled (option 'deviceMode')");
280  return;
281  }
282 
283  // if device is misconfigured, always assume release mode
284  bool rndMode = false;
285 
286  if ((deviceMode & Ssu::RndMode) == Ssu::RndMode)
287  rndMode = true;
288 
289  // get list of device-specific repositories...
290  QStringList repositoryList = repos(rndMode);
291 
292  zypp::RepoManager zyppManager;
293  std::list<zypp::RepoInfo> zyppRepos = zyppManager.knownRepositories();
294 
295  // strict mode enabled -> delete all repositories not prefixed by ssu
296  // assume configuration error if there are no device repos, and don't delete
297  // anything, even in strict mode
298  if ((deviceMode & Ssu::LenientMode) != Ssu::LenientMode && !repositoryList.isEmpty()) {
299  foreach (zypp::RepoInfo zyppRepo, zyppRepos) {
300  if (zyppRepo.filepath().basename().substr(0, 4) != "ssu_") {
301  ssuLog->print(LOG_INFO, "Strict mode enabled, removing unmanaged repository " + QString::fromStdString(zyppRepo.name()));
302  zyppManager.removeRepository(zyppRepo);
303  }
304  }
305  }
306 
307  // ... delete all ssu-managed repositories not valid for this device ...
308  zyppRepos = zyppManager.knownRepositories();
309  foreach (zypp::RepoInfo zyppRepo, zyppRepos) {
310  if (zyppRepo.filepath().basename().substr(0, 4) == "ssu_") {
311  QStringList parts = QString::fromStdString(zyppRepo.filepath().basename()).split("_");
312  // repo file structure is ssu_<reponame>_<rnd|release>.repo -> splits to 3 parts
313  if (parts.count() == 3) {
314  if (!repositoryList.contains(parts.at(1)) ||
315  parts.at(2) != (rndMode ? "rnd.repo" : "release.repo" )) {
316  // This will also remove metadata and cached packages from this repo
317  zyppManager.removeRepository(zyppRepo);
318  }
319  } else {
320  // This will also remove metadata and cached packages from this repo
321  zyppManager.removeRepository(zyppRepo);
322  }
323  }
324  }
325 
326  // ... and create all repositories required for this device
327  foreach (const QString &repo, repositoryList) {
328  // repo should be used where a unique identifier for silly human brains, or
329  // zypper is required. repoName contains the shortened form for ssu use
330  QString repoName = repo;
331  QString debugSplit;
332  if (repo.endsWith("-debuginfo")) {
333  debugSplit = "&debug";
334  repoName = repo.left(repo.size() - 10);
335  }
336 
337  // Not using libzypp to create repo files because it does not support
338  // file name being different than the repo name/alias (ssu_ prefix)
339  QString repoFilePath = QString("%1/ssu_%2_%3.repo")
340  .arg(Sandbox::map(ZYPP_REPO_PATH))
341  .arg(repo)
342  .arg(rndMode ? "rnd" : "release");
343 
344  if (url(repoName, rndMode).isEmpty()) {
345  // TODO, repositories should only be disabled if they're not required
346  // for this machine. For required repositories error is better
347  QTextStream qerr(stderr);
348  qerr << "Repository " << repo << " does not contain valid URL, skipping and disabling." << endl;
349  disable(repo);
350  QFile(repoFilePath).remove();
351  continue;
352  }
353 
354  QFile repoFile(repoFilePath);
355 
356  if (repoFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
357  QTextStream out(&repoFile);
358  // TODO, add -rnd or -release if we want to support having rnd and
359  // release enabled at the same time
360  out << "[" << repo << "]" << endl
361  << "name=" << repo << endl
362  << "failovermethod=priority" << endl
363  << "type=rpm-md" << endl
364  << "gpgcheck=0" << endl
365  << "enabled=1" << endl;
366 
367  if (rndMode)
368  out << "baseurl=plugin:ssu?rnd&repo=" << repoName << debugSplit << endl;
369  else
370  out << "baseurl=plugin:ssu?repo=" << repoName << debugSplit << endl;
371 
372  out.flush();
373  }
374  }
375 }
376 
377 QStringList SsuRepoManager::repoVariables(QHash<QString, QString> *storageHash, bool rnd)
378 {
379  SsuVariables var;
380  SsuCoreConfig *settings = SsuCoreConfig::instance();
381  QStringList configSections;
383 
384  // fill in all arbitrary repo specific variables from ssu.ini
385  var.variableSection(settings, "repository-url-variables", storageHash);
386 
387  // fill in all global variables from ssu.ini
388  // TODO: should be handled somewhere in core variable logic once variables
389  // are more widely used outside of repository urls, for now this is
390  // just for easier migration to "full" variable usage at a later point.
391  var.variableSection(settings, "global-variables", storageHash);
392 
393  // add/overwrite some of the variables with sane ones
394  if (rnd) {
395  storageHash->insert("flavour",
396  repoSettings.value(
397  settings->flavour() + "-flavour/flavour-pattern").toString());
398  storageHash->insert("flavourPattern",
399  repoSettings.value(
400  settings->flavour() + "-flavour/flavour-pattern").toString());
401  storageHash->insert("flavourName", settings->flavour());
402  configSections << settings->flavour() + "-flavour" << "rnd" << "all";
403 
404  // Make it possible to give any values with the flavour as well.
405  // These values can be overridden later with domain if needed.
406  var.variableSection(&repoSettings, settings->flavour() + "-flavour", storageHash);
407  } else {
408  configSections << "release" << "all";
409  }
410 
411  storageHash->insert("release", settings->release(rnd));
412 
413  if (!storageHash->contains("debugSplit"))
414  storageHash->insert("debugSplit", "packages");
415 
416  if (!storageHash->contains("arch"))
417  storageHash->insert("arch", settings->value("arch").toString());
418 
419  return configSections;
420 }
421 
422 // RND repos have flavour (devel, testing, release), and release (latest, next)
423 // Release repos only have release (latest, next, version number)
424 QString SsuRepoManager::url(const QString &repoName, bool rndRepo,
425  QHash<QString, QString> repoParameters,
426  QHash<QString, QString> parametersOverride)
427 {
428  SsuDeviceInfo deviceInfo;
429 
430  // set debugSplit for incorrectly configured debuginfo repositories (debugSplit
431  // should already be passed by the url resolver); might be overriden later on,
432  // if required
433  if (repoName.endsWith("-debuginfo") && !repoParameters.contains("debugSplit"))
434  repoParameters.insert("debugSplit", "debug");
435 
436  QStringList configSections = repoVariables(&repoParameters, rndRepo);
437 
438  // Override device model (and therefore all the family, ... stuff)
439  if (parametersOverride.contains("model"))
440  deviceInfo.setDeviceModel(parametersOverride.value("model"));
441 
442  repoParameters.insert("deviceFamily", deviceInfo.deviceFamily());
443  repoParameters.insert("deviceModel", deviceInfo.deviceModel());
444  repoParameters.insert("deviceVariant", deviceInfo.deviceVariant(true));
445 
446  QString adaptationRepoName = deviceInfo.adaptationVariables(repoName, &repoParameters);
447 
448  SsuCoreConfig *settings = SsuCoreConfig::instance();
449  QString domain;
450 
451  if (parametersOverride.contains("domain")) {
452  domain = parametersOverride.value("domain");
453  domain.replace("-", ":");
454  } else {
455  domain = settings->domain();
456  }
457 
458  repoParameters.insert("brand", settings->brand());
459 
460  // variableSection does autodetection for the domain default section
462  SsuVariables var;
463  var.variableSection(&repoSettings, domain + "-domain", &repoParameters);
464 
465  // override arbitrary variables, mostly useful for generating mic URLs
466  QHash<QString, QString>::const_iterator i = parametersOverride.constBegin();
467  while (i != parametersOverride.constEnd()) {
468  repoParameters.insert(i.key(), i.value());
469  i++;
470  }
471 
472  // search for URLs for repositories. Lookup order is:
473  // 1. overrides in ssu.ini
474  // 2. URLs from features
475  // 3. URLs from repos.ini
476 
477  SsuFeatureManager featureManager;
478  QString r;
479 
480  if (settings->contains("repository-urls/" + adaptationRepoName)) {
481  r = settings->value("repository-urls/" + adaptationRepoName).toString();
482  } else if (!featureManager.url(adaptationRepoName, rndRepo).isEmpty()) {
483  r = featureManager.url(adaptationRepoName, rndRepo);
484  } else {
485  foreach (const QString &section, configSections) {
486  repoSettings.beginGroup(section);
487  if (repoSettings.contains(adaptationRepoName)) {
488  r = repoSettings.value(adaptationRepoName).toString();
489  repoSettings.endGroup();
490  break;
491  }
492  repoSettings.endGroup();
493  }
494  }
495 
496  return var.resolveString(r, &repoParameters);
497 }
SsuFeatureManager
Definition: ssufeaturemanager.h:18
SsuRepoManager::enable
int enable(const QString &repo)
Definition: ssurepomanager.cpp:113
SsuRepoManager::url
QString url(const QString &repoName, bool rndRepo=false, QHash< QString, QString > repoParameters=QHash< QString, QString >(), QHash< QString, QString > parametersOverride=QHash< QString, QString >())
Definition: ssurepomanager.cpp:424
SsuVariables::variableSection
void variableSection(const QString &section, QHash< QString, QString > *storageHash)
Definition: ssuvariables.cpp:173
SsuSettings
Definition: ssusettings_p.h:30
SsuDeviceInfo::repos
QStringList repos(bool rnd=false, int filter=Ssu::NoFilter)
Definition: ssudeviceinfo.cpp:411
SsuCoreConfig::domain
Q_INVOKABLE QString domain(bool pretty=false)
Definition: ssucoreconfig.cpp:73
Ssu::NoFilter
@ NoFilter
All repositories (global + user)
Definition: ssu.h:50
SsuLog
Definition: ssulog_p.h:15
ssuvariables_p.h
SsuDeviceInfo::deviceFamily
Q_INVOKABLE QString deviceFamily()
Definition: ssudeviceinfo.cpp:150
SsuVariables::variable
QVariant variable(const QString &section, const QString &key)
Definition: ssuvariables.cpp:150
Ssu::RndMode
@ RndMode
Enable RnD mode for device.
Definition: ssu.h:68
SsuRepoManager::add
int add(const QString &repo, const QString &repoUrl=QString())
Definition: ssurepomanager.cpp:49
Ssu::UpdateMode
@ UpdateMode
Do repo isolation and similar bits important for updating devices.
Definition: ssu.h:71
ssu.h
ZYPP_REPO_PATH
#define ZYPP_REPO_PATH
Path to zypper repo configuration.
Definition: constants.h:28
Ssu::DisableRepoManager
@ DisableRepoManager
Disable automagic repository management.
Definition: ssu.h:67
ssurepomanager.h
SSU_REPO_CONFIGURATION
#define SSU_REPO_CONFIGURATION
Path to the ssu domain configuration file.
Definition: constants.h:14
SsuRepoManager::remove
int remove(const QString &repo)
Definition: ssurepomanager.cpp:136
ssucoreconfig_p.h
SsuRepoManager::caCertificatePath
static QString caCertificatePath(const QString &domain=QString())
Definition: ssurepomanager.cpp:78
SsuRepoManager::repoVariables
QStringList repoVariables(QHash< QString, QString > *storageHash, bool rnd=false)
Definition: ssurepomanager.cpp:377
SsuRepoManager::disable
int disable(const QString &repo)
Definition: ssurepomanager.cpp:96
SsuCoreConfig
Definition: ssucoreconfig_p.h:27
SsuRepoManager::repos
QStringList repos(int filter=Ssu::NoFilter)
Definition: ssurepomanager.cpp:169
Ssu::LenientMode
@ LenientMode
Disable strict mode (i.e., keep unmanaged repositories)
Definition: ssu.h:70
SsuCoreConfig::brand
Q_INVOKABLE QString brand()
Definition: ssucoreconfig.cpp:85
SsuDeviceInfo::adaptationVariables
QString adaptationVariables(const QString &adaptationName, QHash< QString, QString > *storageHash)
Definition: ssudeviceinfo.cpp:51
SsuLog::print
void print(int priority, const QString &message)
Definition: ssulog.cpp:27
SsuDeviceInfo::setDeviceModel
Q_INVOKABLE void setDeviceModel(const QString &model=QString())
Definition: ssudeviceinfo.cpp:475
sandbox_p.h
SsuCoreConfig::flavour
Q_INVOKABLE QString flavour()
Definition: ssucoreconfig.cpp:57
SsuDeviceInfo
Definition: ssudeviceinfo.h:17
ssufeaturemanager.h
Ssu::AppInstallMode
@ AppInstallMode
Do repo isolation, but keep store repository enabled.
Definition: ssu.h:72
ssudeviceinfo.h
SsuCoreConfig::deviceMode
Q_INVOKABLE Ssu::DeviceModeFlags deviceMode()
Definition: ssucoreconfig.cpp:65
SsuRepoManager::update
void update()
Definition: ssurepomanager.cpp:268
SsuCoreConfig::release
Q_INVOKABLE QString release(bool rnd=false)
Definition: ssucoreconfig.cpp:103
SsuVariables
Definition: ssuvariables_p.h:16
SsuDeviceInfo::deviceModel
Q_INVOKABLE QString deviceModel()
Definition: ssudeviceinfo.cpp:185
Ssu::BoardFilterUserBlacklist
@ BoardFilterUserBlacklist
Only global repositories, with user blacklist applied.
Definition: ssu.h:53
ssusettings_p.h
ssulog_p.h
SSU_REPO_CONFIGURATION_DIR
#define SSU_REPO_CONFIGURATION_DIR
Path to config.d for ssu domain configurations.
Definition: constants.h:16
SsuDeviceInfo::deviceVariant
Q_INVOKABLE QString deviceVariant(bool fallback=false)
Definition: ssudeviceinfo.cpp:170
Ssu::UserFilter
@ UserFilter
Only user configured repositories.
Definition: ssu.h:51
SsuVariables::resolveString
static QString resolveString(const QString &pattern, QHash< QString, QString > *variables, int recursionDepth=0)
Definition: ssuvariables.cpp:39