Skip to content

Commit

Permalink
Configure AppStreams via Activation Keys
Browse files Browse the repository at this point in the history
  • Loading branch information
wweellddeerr committed Jun 17, 2024
1 parent 327a5b0 commit c79a0ea
Show file tree
Hide file tree
Showing 30 changed files with 874 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
import com.redhat.rhn.domain.server.ansible.PlaybookPath;
import com.redhat.rhn.domain.server.virtualhostmanager.VirtualHostManagerNodeInfo;
import com.redhat.rhn.domain.task.Task;
import com.redhat.rhn.domain.token.TokenChannelAppStream;

import com.suse.cloud.domain.PaygDimensionComputation;
import com.suse.cloud.domain.PaygDimensionResult;
Expand Down Expand Up @@ -201,7 +202,8 @@ private AnnotationRegistry() {
CoCoResultTypeConverter.class,
ServerAppStream.class,
AppStream.class,
AppStreamApi.class
AppStreamApi.class,
TokenChannelAppStream.class
);

/**
Expand Down
15 changes: 15 additions & 0 deletions java/code/src/com/redhat/rhn/common/security/acl/Access.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import com.redhat.rhn.domain.role.RoleFactory;
import com.redhat.rhn.domain.server.Server;
import com.redhat.rhn.domain.server.ServerFactory;
import com.redhat.rhn.domain.token.ActivationKey;
import com.redhat.rhn.domain.token.ActivationKeyFactory;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.domain.user.UserFactory;
import com.redhat.rhn.frontend.dto.ChannelPerms;
Expand Down Expand Up @@ -465,6 +467,19 @@ public boolean aclIsModularChannel(Map<String, Object> ctx, String[] params) {
return chan.isModular();
}

/**
* Checks if a given activation key is linked to any modular channel.
* @param ctx acl context (includes the activation key id tid and the user)
* @param params parameters for acl (ignored)
* @return true if it is an activation key associated with any modular channel.
*/
public boolean aclHasModularChannel(Map<String, Object> ctx, String[] params) {
Long tid = getAsLong(ctx.get("tid"));
User user = (User) ctx.get("user");
ActivationKey activationKey = ActivationKeyFactory.lookupById(tid, user.getOrg());
return activationKey.getChannels().stream().anyMatch(Channel::isModular);
}

/**
* Returns true if the user is channel admin of the corresponding channel.
* If the channel is a vendor channel, the return value is false.
Expand Down
114 changes: 114 additions & 0 deletions java/code/src/com/redhat/rhn/domain/token/TokenChannelAppStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (c) 2024 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.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.rhn.domain.token;

import com.redhat.rhn.domain.channel.Channel;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

@Entity
@Table(name = "suseRegTokenChannelAppStream")
public class TokenChannelAppStream {

/**
* Constructs a TokenChannelAppStream instance.
*/
public TokenChannelAppStream() {
// Default constructor
}

/**
* Constructs a TokenChannelAppStream.
*
* @param tokenIn the token
* @param channelIn the channel
* @param nameIn the name of the appStream module
* @param streamIn the stream of the appStream module
*/
public TokenChannelAppStream(Token tokenIn, Channel channelIn, String nameIn, String streamIn) {
token = tokenIn;
channel = channelIn;
name = nameIn;
stream = streamIn;
}

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "suse_reg_tok_ch_as_id_seq")
@SequenceGenerator(name = "suse_reg_tok_ch_as_id_seq", sequenceName = "suse_reg_tok_ch_as_id_seq",
allocationSize = 1)
private Long id;

@ManyToOne
@JoinColumn(name = "token_id")
private Token token;

@ManyToOne
@JoinColumn(name = "channel_id")
private Channel channel;

@Column(nullable = false)
private String name;

@Column(nullable = false)
private String stream;

public Long getId() {
return id;
}

public void setId(Long idIn) {
id = idIn;
}

public Token getToken() {
return token;
}

public void setToken(Token tokenIn) {
token = tokenIn;
}

public String getName() {
return name;
}

public void setName(String nameIn) {
name = nameIn;
}

public String getStream() {
return stream;
}

public void setStream(String streamIn) {
stream = streamIn;
}

public Channel getChannel() {
return channel;
}

public void setChannel(Channel channelIn) {
channel = channelIn;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public List<ChannelAppStreamsResponse> listModuleStreams(User loggedInUser, Inte
.map(channel -> new ChannelAppStreamsResponse(
channel,
AppStreamsManager.listChannelAppStreams(channel.getId()),
server
server::hasAppStreamModuleEnabled
))
.collect(Collectors.toList());
}
Expand Down
123 changes: 123 additions & 0 deletions java/code/src/com/redhat/rhn/manager/token/ActivationKeyManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.redhat.rhn.domain.server.ServerGroupType;
import com.redhat.rhn.domain.token.ActivationKey;
import com.redhat.rhn.domain.token.ActivationKeyFactory;
import com.redhat.rhn.domain.token.TokenChannelAppStream;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.frontend.struts.Scrubber;
import com.redhat.rhn.manager.channel.ChannelManager;
Expand All @@ -60,6 +61,11 @@
import java.util.Map;
import java.util.Set;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

/**
* ActivationKeyManager
*/
Expand Down Expand Up @@ -565,4 +571,121 @@ public void setupAutoConfigDeployment(ActivationKey key) {
addConfigMgmtPackages(key);
}
}

/**
* Checks if a specific app stream module is enabled for the given activation key and channel.
*
* @param activationKey the activation key containing the registration token for matching
* @param channel the channel to check
* @param module the name of the appStream module to check
* @param stream the stream of the appStream module to check
* @return {@code true} if the app stream module is included in the activation key and channel,
* {@code false} otherwise
*/
public boolean hasAppStreamModuleEnabled(
ActivationKey activationKey,
Channel channel,
String module,
String stream) {
return listTokenChannelAppStreams(activationKey, channel)
.stream()
.anyMatch(it ->
it.getChannel().getId().equals(channel.getId()) &&
it.getName().equals(module) &&
it.getStream().equals(stream)
);
}

/**
* Retrieves a list of {@code TokenChannelAppStream} objects that match the given activation key.
*
* @param activationKey the activation key containing the token to match
* @return a list of {@code TokenChannelAppStream} objects related to the activation key.
*/
public List<TokenChannelAppStream> listTokenChannelAppStreams(ActivationKey activationKey) {
return listTokenChannelAppStreams(activationKey, null);
}

/**
* Retrieves a list of {@code TokenChannelAppStream} objects that match the given activation key and channel.
* If the channel is {@code null}, only the activation key is used as a filter criterion.
*
* @param activationKey the activation key containing the token to match
* @param channel the channel to match; if {@code null}, the channel criterion is ignored
* @return a list of {@code TokenChannelAppStream} objects that match the given criteria
*/
private List<TokenChannelAppStream> listTokenChannelAppStreams(ActivationKey activationKey, Channel channel) {
CriteriaBuilder criteriaBuilder = ActivationKeyFactory.getSession().getCriteriaBuilder();
CriteriaQuery<TokenChannelAppStream> criteriaQuery = criteriaBuilder.createQuery(TokenChannelAppStream.class);
Root<TokenChannelAppStream> root = criteriaQuery.from(TokenChannelAppStream.class);

Predicate tokenPredicate = criteriaBuilder.equal(root.get("token"), activationKey.getToken());
if (channel != null) {
Predicate channelPredicate = criteriaBuilder.equal(root.get("channel"), channel);
criteriaQuery.where(criteriaBuilder.and(tokenPredicate, channelPredicate));
}
else {
criteriaQuery.where(tokenPredicate);
}

return ActivationKeyFactory.getSession().createQuery(criteriaQuery).getResultList();
}

/**
* Saves the specified channel app streams by including and removing the given lists of app streams.
* It will first remove the specified app streams from the channel, and then includes the specified
* app streams to the channel. The app streams are specified as a list of strings in the format "name:stream".
*
* @param activationKey the activation key containing the registration token
* @param channel the channel to which the app streams belong
* @param toInclude the list of app streams to include in the activation key
* @param toRemove the list of app streams to remove from the activation key
*/
public void saveChannelAppStreams(
ActivationKey activationKey,
Channel channel,
List<String> toInclude,
List<String> toRemove) {
removeChannelAppStreams(activationKey, channel, toRemove);
includeChannelAppStreams(activationKey, channel, toInclude);
}

private void removeChannelAppStreams(ActivationKey activationKey, Channel channel, List<String> toRemove) {
var session = ActivationKeyFactory.getSession();
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
var criteriaDelete = criteriaBuilder.createCriteriaDelete(TokenChannelAppStream.class);
var root = criteriaDelete.from(TokenChannelAppStream.class);

Predicate tokenPredicate = criteriaBuilder.equal(root.get("token"), activationKey.getToken());
Predicate channelPredicate = criteriaBuilder.equal(root.get("channel"), channel);

criteriaDelete.where(
criteriaBuilder.and(
tokenPredicate,
channelPredicate,
criteriaBuilder.or(
toRemove
.stream()
.map(appStream -> {
String[] parts = appStream.split(":");
String name = parts[0];
String stream = parts[1];
Predicate namePredicate = criteriaBuilder.equal(root.get("name"), name);
Predicate streamPredicate = criteriaBuilder.equal(root.get("stream"), stream);
return criteriaBuilder.and(namePredicate, streamPredicate);
}).toArray(Predicate[]::new)))
);
session.createQuery(criteriaDelete).executeUpdate();
}

private void includeChannelAppStreams(ActivationKey activationKey, Channel channel, List<String> toInclude) {
toInclude.forEach(appStream -> {
String[] parts = appStream.split(":");
String name = parts[0];
String stream = parts[1];
ActivationKeyFactory.getSession().persist(
new TokenChannelAppStream(activationKey.getToken(), channel, name, stream)
);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static java.util.Collections.emptySet;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.partitioningBy;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

import com.redhat.rhn.GlobalInstanceHolder;
Expand Down Expand Up @@ -46,12 +47,14 @@
import com.redhat.rhn.manager.action.ActionManager;
import com.redhat.rhn.manager.system.SystemManager;
import com.redhat.rhn.manager.system.entitling.SystemEntitlementManager;
import com.redhat.rhn.manager.token.ActivationKeyManager;
import com.redhat.rhn.taskomatic.TaskomaticApiException;

import com.suse.manager.reactor.utils.RhelUtils;
import com.suse.manager.reactor.utils.ValueMap;
import com.suse.manager.webui.controllers.StatesAPI;
import com.suse.manager.webui.controllers.channels.ChannelsUtils;
import com.suse.manager.webui.services.SaltServerActionService;
import com.suse.manager.webui.services.iface.RedhatProductInfo;
import com.suse.manager.webui.services.iface.SystemQuery;
import com.suse.manager.webui.services.pillar.MinionPillarManager;
Expand All @@ -67,6 +70,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -125,6 +129,7 @@ public static void finishRegistration(MinionServer minion, Optional<ActivationKe
activationKey.get().getEntitlements().stream().anyMatch(e -> !e.isBase()));

// Apply initial states asynchronously
Map<String, Object> statesToApplyPillar = new HashMap<>();
List<String> statesToApply = new ArrayList<>();
statesToApply.add(ApplyStatesEventMessage.CERTIFICATE);
statesToApply.add(ApplyStatesEventMessage.CHANNELS);
Expand All @@ -136,11 +141,15 @@ public static void finishRegistration(MinionServer minion, Optional<ActivationKe
// SSH Minions need this to set last booted value.
statesToApply.add(ApplyStatesEventMessage.SYSTEM_INFO);
}

handleActivationKeyAppStreams(activationKey, statesToApply, statesToApplyPillar);

MessageQueue.publish(new ApplyStatesEventMessage(
minion.getId(),
minion.getCreator() != null ? minion.getCreator().getId() : null,
!applyHighstate, // Refresh package list if we're not going to apply the highstate afterwards
statesToApply
statesToApplyPillar,
statesToApply.toArray(new String[0])
));

// Call final highstate to deploy config channels if required
Expand All @@ -160,6 +169,30 @@ public static void finishRegistration(MinionServer minion, Optional<ActivationKe
triggerHardwareRefresh(minion);
}

/**
* Includes appStreams configuration state and its params to the list of applicable states in case
* there is any modular channel linked to the activation key.
* @param activationKey the activation key
* @param statesToApply the current list of applicable states
* @param statesToApplyPillar the current map of pillar
*/
private static void handleActivationKeyAppStreams(
Optional<ActivationKey> activationKey,
List<String> statesToApply,
Map<String, Object> statesToApplyPillar) {
if (activationKey.isPresent() && activationKey.get().getChannels().stream().anyMatch(Channel::isModular)) {
var appStreamsToEnable = ActivationKeyManager.getInstance().listTokenChannelAppStreams(activationKey.get());
if (!appStreamsToEnable.isEmpty()) {
statesToApply.add(SaltServerActionService.APPSTREAMS_CONFIGURE);
var appStreamsParams = appStreamsToEnable
.stream()
.map(it -> List.of(it.getName(), it.getStream()))
.collect(toList());
statesToApplyPillar.put(SaltServerActionService.PARAM_APPSTREAMS_ENABLE, appStreamsParams);
}
}
}

private static void triggerHardwareRefresh(MinionServer server) {
try {
ActionManager.scheduleHardwareRefreshAction(server.getOrg(), server,
Expand Down
Loading

0 comments on commit c79a0ea

Please sign in to comment.