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