Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure AppStreams via Activation Keys #8909

Merged
merged 7 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 4 additions & 0 deletions java/code/src/com/redhat/rhn/domain/channel/AppStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,8 @@ public Set<AppStreamApi> getRpms() {
public void setRpms(Set<AppStreamApi> rpmsIn) {
rpms = rpmsIn;
}

public String getAppStreamKey() {
return name + ":" + stream;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,14 @@ public void setContactMethod(ContactMethod contactMethodIn) {
this.getToken().setContactMethod(contactMethodIn);
}

/**
* Get the appStreams related to this activation key.
* @return the Set of appStreams
*/
public Set<TokenChannelAppStream> getAppStreams() {
return this.getToken().getAppStreams();
}

/**
* Makes the Activation key prefix that will get
* added to the base key
Expand Down
15 changes: 15 additions & 0 deletions java/code/src/com/redhat/rhn/domain/token/Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public class Token implements Identifiable {
private Set<Channel> channels = new HashSet<>();
private Set<ServerGroup> serverGroups = new HashSet<>();
private Set<TokenPackage> packages = new HashSet<>();
private Set<TokenChannelAppStream> appStreams = new HashSet<>();

/**
* @return Returns the entitlements.
Expand Down Expand Up @@ -440,6 +441,20 @@ public void setPackages(Set<TokenPackage> packagesIn) {
this.packages = packagesIn;
}

/**
* @return the app streams associated with the token
*/
public Set<TokenChannelAppStream> getAppStreams() {
return appStreams;
}

/**
* @param appStreamsIn the app streams to set
*/
public void setAppStreams(Set<TokenChannelAppStream> appStreamsIn) {
appStreams = appStreamsIn;
}

/**
* Clear all packages associated with this token.
*/
Expand Down
117 changes: 117 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,117 @@
/*
* 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 appStreamIn the appStream in the format name:stream
*/
public TokenChannelAppStream(Token tokenIn, Channel channelIn, String appStreamIn) {
token = tokenIn;
channel = channelIn;
name = appStreamIn.split(":")[0];
stream = appStreamIn.split(":")[1];
}

@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;
}

public String getAppStream() {
return name + ":" + stream;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
<many-to-many class="com.redhat.rhn.domain.channel.Channel"
column="channel_id"/>
</set>
<set name="appStreams" table="suseRegTokenChannelAppStream" inverse="true" cascade="all-delete-orphan">
<key column="token_id"/>
<one-to-many class="com.redhat.rhn.domain.token.TokenChannelAppStream"/>
</set>

<set name="serverGroups" table="rhnRegTokenGroups">
<key column="token_id"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.frontend.xmlrpc;

import com.redhat.rhn.FaultException;

public class DuplicateAppStreamException extends FaultException {

/**
* Constructor
* @param message exception message
*/
public DuplicateAppStreamException(String message) {
super(-309, "duplicateStream", message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,72 @@ public int removePackages(User loggedInUser, String key,
return 1;
}

/**
* Add app streams to an activation key.
*
* @param loggedInUser The current user
* @param key The activation key to act upon
* @param appStreams List of app streams to be added to this activation key
* @return 1 on success, exception thrown otherwise
*
* @apidoc.doc Add app streams to an activation key. If any of the provided app streams is not available in the
* channels of the activation key, the request will fail.
* @apidoc.param #session_key()
* @apidoc.param #param("string", "key")
* @apidoc.param #array_begin("appStreams")
* #struct_begin("Module Stream")
* #prop("string", "module")
* #prop("string", "stream")
* #struct_end()
* #array_end()
* @apidoc.returntype #return_int_success()
*/
public int addAppStreams(User loggedInUser, String key, List<Map<String, String>> appStreams) {
ActivationKeyManager akm = ActivationKeyManager.getInstance();
ActivationKey activationKey = lookupKey(key, loggedInUser);
List<String> appStreamsKeys = appStreams.stream()
.map(it -> it.get("module") + ":" + it.get("stream"))
.collect(Collectors.toList());
Map<String, Channel> channelsProviding = akm.getChannelsProvidingAppStreams(activationKey, appStreamsKeys);
Map<Channel, List<String>> toIncludeMap = channelsProviding.entrySet().stream()
.collect(Collectors.groupingBy(
Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())
));
toIncludeMap.forEach((channel, toInclude) ->
akm.saveChannelAppStreams(activationKey, channel, toInclude, Collections.emptyList())
);
return 1;
}

/**
* Remove app streams from an activation key.
*
* @param loggedInUser The current user
* @param key The activation key to act upon
* @param appStreams List of app streams to be removed from this activation key
* @return 1 on success, exception thrown otherwise
*
* @apidoc.doc Remove app streams from an activation key.
* @apidoc.param #session_key()
* @apidoc.param #param("string", "key")
* @apidoc.param #array_begin("appStreams")
* #struct_begin("Module Stream")
* #prop("string", "module")
* #prop("string", "stream")
* #struct_end()
* #array_end()
* @apidoc.returntype #return_int_success()
*/
public int removeAppStreams(User loggedInUser, String key, List<Map<String, String>> appStreams) {
ActivationKeyManager akm = ActivationKeyManager.getInstance();
ActivationKey activationKey = lookupKey(key, loggedInUser);
var toRemove = appStreams.stream().map(it -> it.get("module") + ":" + it.get("stream"))
.collect(Collectors.toList());
akm.removeAppStreams(activationKey, toRemove);
return 1;
}

/**
* Return a list of activation key structs that are visible to the requesting user.
* @param loggedInUser The current user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.redhat.rhn.domain.server.ServerGroup;
import com.redhat.rhn.domain.server.ServerGroupType;
import com.redhat.rhn.domain.token.Token;
import com.redhat.rhn.domain.token.TokenChannelAppStream;
import com.redhat.rhn.domain.token.TokenPackage;

import com.suse.manager.api.ApiResponseSerializer;
Expand All @@ -29,6 +30,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* ActivationKeySerializer
Expand Down Expand Up @@ -116,6 +118,14 @@ static void populateTokenInfo(Token token, SerializationBuilder builder) {
}
packages.add(pkgMap);
}

// Channel label is the key and a List of appStreams (name:stream) for each channel.
Map<String, List<String>> appStreams = token.getAppStreams().stream()
.collect(Collectors.groupingBy(
tokenChannelAppStream -> tokenChannelAppStream.getChannel().getLabel(),
Collectors.mapping(TokenChannelAppStream::getAppStream, Collectors.toList())
));

builder.add("description", token.getNote());

int usageLimit = 0;
Expand All @@ -130,6 +140,7 @@ static void populateTokenInfo(Token token, SerializationBuilder builder) {
builder.add("server_group_ids", serverGroupIds);
builder.add("package_names", packageNames);
builder.add("packages", packages);
builder.add("app_streams", appStreams);

Boolean universalDefault = token.isOrgDefault();
builder.add("universal_default", universalDefault);
Expand Down
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
Loading
Loading