Skip to content

Commit

Permalink
Merge pull request #9701 from mcalmer/issv3-align-custom-channels-at-…
Browse files Browse the repository at this point in the history
…sync

Issv3 align custom channels at sync
  • Loading branch information
mcalmer authored Feb 6, 2025
2 parents e7891f4 + baffedf commit 2e224d2
Show file tree
Hide file tree
Showing 23 changed files with 1,032 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
import com.redhat.rhn.taskomatic.task.payg.beans.PaygProductInfo;

import com.suse.cloud.CloudPaygManager;
import com.suse.manager.hub.HubManager;
import com.suse.manager.model.hub.IssHub;
import com.suse.manager.webui.services.pillar.MinionGeneralPillarGenerator;
import com.suse.mgrsync.MgrSyncStatus;
import com.suse.salt.netapi.parser.JsonParser;
Expand Down Expand Up @@ -868,11 +870,12 @@ public void refreshRepositoriesAuthentication(
List<Long> availableRepoIds = SCCCachingFactory.lookupRepositories().stream()
.map(SCCRepository::getSccId)
.toList();
List<SCCRepositoryJson> ptfRepos = repositories.stream()
Map<Boolean, List<SCCRepositoryJson>> ptfOrCustomRepos = repositories.stream()
.filter(r -> !availableRepoIds.contains(r.getSCCId()))
.filter(SCCRepositoryJson::isPtfRepository)
.toList();
generatePtfChannels(ptfRepos);
.collect(Collectors.groupingBy(SCCRepositoryJson::isPtfRepository,
Collectors.toList()));

generatePtfChannels(ptfOrCustomRepos.getOrDefault(Boolean.TRUE, Collections.emptyList()));
Map<Long, SCCRepository> availableReposById = SCCCachingFactory.lookupRepositories().stream()
.collect(Collectors.toMap(SCCRepository::getSccId, r -> r));

Expand Down Expand Up @@ -979,6 +982,11 @@ public void refreshRepositoriesAuthentication(
source.getCredentials(SCCCredentials.class)
.ifPresent(scc -> repoIdsFromCredential.addAll(refreshOESRepositoryAuth(scc, mirrorUrl, oesRepos)));

// Custom Channels
source.getCredentials(SCCCredentials.class)
.ifPresent(scc -> refreshCustomRepoAuthentication(
ptfOrCustomRepos.getOrDefault(Boolean.FALSE, Collections.emptyList()), scc));

//DELETE OLD
// check if we have to remove auths which exists before
List<SCCRepositoryAuth> authList = SCCCachingFactory.lookupRepositoryAuthByCredential(source);
Expand All @@ -991,6 +999,54 @@ public void refreshRepositoriesAuthentication(
});
}

private void refreshCustomRepoAuthentication(List<SCCRepositoryJson> customReposIn, SCCCredentials creds) {
IssHub issHub = creds.getIssHub();
if (issHub == null) {
LOG.debug("Only Peripheral server manage custom channels via SCC API");
return;
}
boolean metadataSigned = StringUtils.isNotBlank(issHub.getGpgKey());
for (SCCRepositoryJson repo : customReposIn) {
Channel customChannel = ChannelFactory.lookupByLabel(repo.getName());
if (!isValidCustomChannel(repo, customChannel)) {
LOG.error("Invalid custom repo/channel {} - {}", repo, customChannel);
continue;
}
Set<ContentSource> css = customChannel.getSources();
if (css.isEmpty()) {
// new channel; need to add the source
ContentSource source = new ContentSource();
source.setLabel(customChannel.getLabel());
source.setOrg(customChannel.getOrg());
source.setSourceUrl(repo.getUrl());
source.setType(ChannelManager.findCompatibleContentSourceType(customChannel.getChannelArch()));
source.setMetadataSigned(metadataSigned);
ChannelFactory.save(source);

css.add(source);
customChannel.setSources(css);
ChannelFactory.save(customChannel);
}
else if (css.size() == 1) {
// found the repo; update the URL
ContentSource source = css.iterator().next();
source.setSourceUrl(repo.getUrl());
source.setMetadataSigned(metadataSigned);
ChannelFactory.save(source);
}
else {
LOG.error("Multiple repositories not allowed for this custom channel {}. Skipping",
customChannel.getName());
}
}
}

private boolean isValidCustomChannel(SCCRepositoryJson repoIn, Channel customChannelIn) {
return customChannelIn != null && repoIn != null &&
Objects.equals(repoIn.getSCCId(), HubManager.CUSTOM_REPO_FAKE_SCC_ID) &&
Objects.equals(customChannelIn.getLabel(), repoIn.getName());
}

private void generatePtfChannels(List<SCCRepositoryJson> repositories) {
List<SCCRepository> reposToSave = new ArrayList<>();
List<ChannelTemplate> templatesToSave = new ArrayList<>();
Expand Down
12 changes: 12 additions & 0 deletions java/code/src/com/redhat/rhn/taskomatic/TaskomaticApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -858,4 +858,16 @@ public void scheduleSingleRootCaCertUpdate(Map<String, String> filenameToRootCaC
paramList.put("filename_to_root_ca_cert_map", sanitisedFilenameToRootCaCertMap);
invoke("tasko.scheduleSingleSatBunchRun", "root-ca-cert-update-bunch", paramList);
}

/**
* Schedule an import of a GPG key.
* @param gpgKey the GPG key (armored text)
* @throws TaskomaticApiException if there was an error
*/
public void scheduleSingleGpgKeyImport(String gpgKey) throws TaskomaticApiException {
if (StringUtils.isBlank(gpgKey)) {
return;
}
invoke("tasko.scheduleSingleSatBunchRun", "custom-gpg-key-import-bunch", Map.of("gpg-key", gpgKey));
}
}
47 changes: 47 additions & 0 deletions java/code/src/com/redhat/rhn/taskomatic/task/GpgImportTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2025 SUSE LLC
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*/
package com.redhat.rhn.taskomatic.task;

import com.suse.utils.CertificateUtils;

import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
* Taskomatic task that import a GPG keys to the customer keyring
* After saving the GPG key, the system configuration is refreshed.
*/
public class GpgImportTask extends RhnJavaJob {

@Override
public String getConfigNamespace() {
return "gpg-update";
}

/**
* {@inheritDoc}
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
final JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
if (!jobDataMap.containsKey("gpg-key")) {
log.error("No GPG key provided");
return;
}
try {
CertificateUtils.importGpgKey((String)jobDataMap.get("gpg-key"));
}
catch (Exception e) {
log.error("Importing the GPG key failed", e);
}
}
}
14 changes: 12 additions & 2 deletions java/code/src/com/suse/manager/hub/DefaultHubInternalClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ public DefaultHubInternalClient(String remoteHostIn, String tokenIn, Optional<Ce
}

@Override
public void registerHub(String token, String rootCA) throws IOException {
invokePostMethod("hub/sync", "registerHub", new RegisterJson(token, rootCA), Void.class);
public void registerHub(String token, String rootCA, String gpgKey) throws IOException {
invokePostMethod("hub/sync", "registerHub", new RegisterJson(token, rootCA, gpgKey), Void.class);
}

@Override
Expand All @@ -85,6 +85,16 @@ public void storeReportDbCredentials(String username, String password) throws IO
Map.of("username", username, "password", password), Void.class);
}

@Override
public String replaceTokens(String newHubToken) throws IOException {
return invokePostMethod("hub/sync", "replaceTokens", newHubToken, String.class);
}

@Override
public void deregister() throws IOException {
invokePostMethod("hub/sync", "deregister", null, Void.class);
}

private <Res> Res invokeGetMethod(String namespace, String apiMethod, Class<Res> responseClass)
throws IOException {
HttpGet request = new HttpGet(("https://%s/rhn/%s/%s").formatted(remoteHost, namespace, apiMethod));
Expand Down
57 changes: 56 additions & 1 deletion java/code/src/com/suse/manager/hub/HubController.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import static com.suse.manager.hub.HubSparkHelper.allowingOnlyHub;
import static com.suse.manager.hub.HubSparkHelper.allowingOnlyPeripheral;
import static com.suse.manager.hub.HubSparkHelper.allowingOnlyRegistered;
import static com.suse.manager.hub.HubSparkHelper.allowingOnlyUnregistered;
import static com.suse.manager.hub.HubSparkHelper.usingTokenAuthentication;
import static com.suse.manager.webui.utils.SparkApplicationHelper.asJson;
Expand All @@ -39,6 +40,7 @@
import com.suse.manager.model.hub.RegisterJson;
import com.suse.manager.model.hub.SCCCredentialsJson;
import com.suse.manager.webui.controllers.ECMAScriptDateAdapter;
import com.suse.manager.webui.utils.token.TokenBuildingException;
import com.suse.manager.webui.utils.token.TokenParsingException;

import com.google.gson.Gson;
Expand Down Expand Up @@ -86,8 +88,11 @@ public HubController(HubManager hubManagerIn) {
*/
public void initRoutes() {
post("/hub/ping", asJson(usingTokenAuthentication(this::ping)));
post("/hub/sync/deregister", asJson(usingTokenAuthentication(allowingOnlyRegistered(this::deregister))));
post("/hub/sync/registerHub", asJson(usingTokenAuthentication(allowingOnlyUnregistered(this::registerHub))));
post("/hub/sync/replaceTokens", asJson(usingTokenAuthentication(allowingOnlyHub(this::replaceTokens))));
post("/hub/sync/storeCredentials", asJson(usingTokenAuthentication(allowingOnlyHub(this::storeCredentials))));
post("/hub/sync/setHubDetails", asJson(usingTokenAuthentication(allowingOnlyHub(this::setHubDetails))));
get("/hub/managerinfo", asJson(usingTokenAuthentication(allowingOnlyHub(this::getManagerInfo))));
post("/hub/storeReportDbCredentials",
asJson(usingTokenAuthentication(allowingOnlyHub(this::setReportDbCredentials))));
Expand All @@ -99,6 +104,56 @@ public void initRoutes() {
asJson(usingTokenAuthentication(allowingOnlyPeripheral(this::listAllPeripheralChannels))));
}

private String setHubDetails(Request request, Response response, IssAccessToken accessToken) {
Map<String, String> data = GSON.fromJson(request.body(), Map.class);

try {
hubManager.updateServerData(accessToken, accessToken.getServerFqdn(), IssRole.HUB, data);
}
catch (IllegalArgumentException ex) {
LOGGER.error("Invalid data provided: ", ex);
return badRequest(response, "Invalid data");
}
catch (Exception ex) {
LOGGER.error("Internal Server Error: ", ex);
return internalServerError(response, "Internal Server Error");
}
return success(response);
}

private String deregister(Request request, Response response, IssAccessToken accessToken) {
// request to delete the local access for the requesting server.
try {
hubManager.deleteIssServerLocal(accessToken, accessToken.getServerFqdn());
}
catch (Exception ex) {
LOGGER.error("Internal Server Error: ", ex);
return internalServerError(response, "Internal Server Error");
}
return success(response);
}

private String replaceTokens(Request request, Response response, IssAccessToken currentAccessToken) {
String newRemoteToken = GSON.fromJson(request.body(), String.class);
if (newRemoteToken.isBlank()) {
LOGGER.error("Bad Request: invalid data");
return badRequest(response, "Invalid data");
}
try {
String newLocalToken = hubManager.replaceTokens(currentAccessToken, newRemoteToken);
return success(response, newLocalToken);
}
catch (TokenParsingException ex) {
LOGGER.error("Unable to parse the received token for server {}", currentAccessToken.getServerFqdn());
return badRequest(response, "The specified token is not parseable");
}
catch (TokenBuildingException ex) {
LOGGER.error("Unable to build token");
return badRequest(response, "The token could not be build");
}

}

private String removeReportDbCredentials(Request request, Response response, IssAccessToken token) {
Map<String, String> creds = GSON.fromJson(request.body(), Map.class);
String dbname = Config.get().getString(ConfigDefaults.REPORT_DB_NAME, "");
Expand Down Expand Up @@ -176,7 +231,7 @@ private String registerHub(Request request, Response response, IssAccessToken to

try {
hubManager.storeAccessToken(token, tokenToStore);
hubManager.saveNewServer(token, IssRole.HUB, registerRequest.getRootCA());
hubManager.saveNewServer(token, IssRole.HUB, registerRequest.getRootCA(), registerRequest.getGpgKey());

return success(response);
}
Expand Down
18 changes: 17 additions & 1 deletion java/code/src/com/suse/manager/hub/HubInternalClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ public interface HubInternalClient {
* Register a remote server as a hub
* @param token the token issued by the remote server to grant access
* @param rootCA the root certificate, if needed
* @param gpgKey the gpg key, if needed
* @throws IOException when the communication fails
*/
void registerHub(String token, String rootCA) throws IOException;
void registerHub(String token, String rootCA, String gpgKey) throws IOException;

/**
* Store the SCC credentials on the remote peripheral server
Expand All @@ -51,4 +52,19 @@ public interface HubInternalClient {
*/
void storeReportDbCredentials(String username, String password) throws IOException;

/**
* De-register the calling server from the remote side
*
* @throws IOException when the communication fails
*/
void deregister() throws IOException;

/**
* Replace the hub token on the remote peripheral server and get a new peripheral token back
* @param newHubToken the new hub token
* @return return the new peripheral token
* @throws IOException when the communication fails
*/
String replaceTokens(String newHubToken) throws IOException;

}
Loading

0 comments on commit 2e224d2

Please sign in to comment.