diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java index d7d0ef70c..0984fe6fe 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java @@ -21,6 +21,11 @@ public interface TenantAware { */ String getCurrentTenant(); + /** + * @return the username of the currently logged-in user + */ + String getCurrentUsername(); + /** * Gives the possibility to run a certain code under a specific given * {@code tenant}. Only the given {@link TenantRunner} is executed under the diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index 27d009e08..88b6a127b 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -76,13 +76,14 @@ public interface DeploymentManagement { * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) - List assignDistributionSets( - @Valid @NotEmpty List deploymentRequests); + List assignDistributionSets(@Valid @NotEmpty List deploymentRequests); /** * Assigns {@link DistributionSet}s to {@link Target}s according to the * {@link DeploymentRequest}. * + * @param initiatedBy + * the username of the user who initiated the assignment * @param deploymentRequests * information about all target-ds-assignments that shall be made * @param actionMessage @@ -107,7 +108,7 @@ public interface DeploymentManagement { * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) - List assignDistributionSets( + List assignDistributionSets(String initiatedBy, @Valid @NotEmpty List deploymentRequests, String actionMessage); /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java index 1c6f2e568..3daa64f9c 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java @@ -130,6 +130,11 @@ public interface Action extends TenantAwareBaseEntity { */ String getExternalRef(); + /** + * @return the username that initiated this action (directly or indirectly) + */ + String getInitiatedBy(); + /** * checks if the {@link #getForcedTime()} is hit by the given * {@code hitTimeMillis}, by means if the given milliseconds are greater diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java index e4b51e47a..3bc8f3ea8 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java @@ -77,4 +77,8 @@ public interface TargetFilterQuery extends TenantAwareBaseEntity { */ Optional getAutoAssignWeight(); + /** + * @return the user that triggered the auto assignment + */ + String getAutoAssignInitiatedBy(); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java index 8d8839f98..1bed104fd 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java @@ -201,8 +201,8 @@ public abstract class AbstractDsAssignmentStrategy { new CancelTargetAssignmentEvent(target, actionId, eventPublisherHolder.getApplicationId()))); } - JpaAction createTargetAction(final TargetWithActionType targetWithActionType, final List targets, - final JpaDistributionSet set) { + JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType, + final List targets, final JpaDistributionSet set) { final Optional optTarget = targets.stream() .filter(t -> t.getControllerId().equals(targetWithActionType.getControllerId())).findFirst(); @@ -219,6 +219,7 @@ public abstract class AbstractDsAssignmentStrategy { actionForTarget.setMaintenanceWindowSchedule(targetWithActionType.getMaintenanceSchedule()); actionForTarget.setMaintenanceWindowDuration(targetWithActionType.getMaintenanceWindowDuration()); actionForTarget.setMaintenanceWindowTimeZone(targetWithActionType.getMaintenanceWindowTimeZone()); + actionForTarget.setInitiatedBy(initiatedBy); return actionForTarget; }).orElseGet(() -> { LOG.warn("Cannot find target for targetWithActionType '{}'.", targetWithActionType.getControllerId()); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java index 8b95644fd..e52308bd1 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java @@ -182,33 +182,34 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl .map(entry -> DeploymentManagement.deploymentRequest(entry.getKey(), entry.getValue()).build()) .collect(Collectors.toList()); - return assignDistributionSets(deploymentRequests, null, offlineDsAssignmentStrategy); + return assignDistributionSets(tenantAware.getCurrentUsername(), deploymentRequests, null, + offlineDsAssignmentStrategy); } @Override @Transactional(isolation = Isolation.READ_COMMITTED) public List assignDistributionSets( final List deploymentRequests) { - return assignDistributionSets(deploymentRequests, null); + return assignDistributionSets(tenantAware.getCurrentUsername(), deploymentRequests, null); } @Override @Transactional(isolation = Isolation.READ_COMMITTED) - public List assignDistributionSets( + public List assignDistributionSets(final String initiatedBy, final List deploymentRequests, final String actionMessage) { WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement) .validate(deploymentRequests); - return assignDistributionSets(deploymentRequests, actionMessage, onlineDsAssignmentStrategy); + return assignDistributionSets(initiatedBy, deploymentRequests, actionMessage, onlineDsAssignmentStrategy); } - private List assignDistributionSets( + private List assignDistributionSets(final String initiatedBy, final List deploymentRequests, final String actionMessage, final AbstractDsAssignmentStrategy strategy) { final List validatedRequests = validateRequestForAssignments(deploymentRequests); final Map> assignmentsByDsIds = convertRequest(validatedRequests); final List results = assignmentsByDsIds.entrySet().stream() - .map(entry -> assignDistributionSetToTargetsWithRetry(entry.getKey(), entry.getValue(), actionMessage, + .map(entry -> assignDistributionSetToTargetsWithRetry(initiatedBy, entry.getKey(), entry.getValue(), actionMessage, strategy)) .collect(Collectors.toList()); strategy.sendDeploymentEvents(results); @@ -238,11 +239,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl } } - private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(final Long dsID, + private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(final String initiatedBy, final Long dsID, final Collection targetsWithActionType, final String actionMessage, final AbstractDsAssignmentStrategy assignmentStrategy) { final RetryCallback retryCallback = retryContext -> assignDistributionSetToTargets( - dsID, targetsWithActionType, actionMessage, assignmentStrategy); + initiatedBy, dsID, targetsWithActionType, actionMessage, assignmentStrategy); return retryTemplate.execute(retryCallback); } @@ -260,6 +261,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl * status to {@link TargetUpdateStatus#IN_SYNC}
* D. does not send a {@link TargetAssignDistributionSetEvent}.
* + * @param initiatedBy + * the username of the user who initiated the assignment * @param dsID * the ID of the distribution set to assign * @param targetsWithActionType @@ -274,7 +277,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl * {@link SoftwareModuleType} are not assigned as define by the * {@link DistributionSetType}. */ - private DistributionSetAssignmentResult assignDistributionSetToTargets(final Long dsID, + private DistributionSetAssignmentResult assignDistributionSetToTargets(final String initiatedBy, final Long dsID, final Collection targetsWithActionType, final String actionMessage, final AbstractDsAssignmentStrategy assignmentStrategy) { @@ -296,8 +299,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl final List existingTargetsWithActionType = targetsWithActionType.stream() .filter(target -> existingTargetIds.contains(target.getControllerId())).collect(Collectors.toList()); - final List assignedActions = doAssignDistributionSetToTargets(existingTargetsWithActionType, - actionMessage, assignmentStrategy, distributionSetEntity, targetEntities); + final List assignedActions = doAssignDistributionSetToTargets(initiatedBy, + existingTargetsWithActionType, actionMessage, assignmentStrategy, distributionSetEntity, + targetEntities); return buildAssignmentResult(distributionSetEntity, assignedActions, existingTargetsWithActionType.size()); } @@ -310,7 +314,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl Collections.emptyList()); } - private List doAssignDistributionSetToTargets( + private List doAssignDistributionSetToTargets(final String initiatedBy, final Collection targetsWithActionType, final String actionMessage, final AbstractDsAssignmentStrategy assignmentStrategy, final JpaDistributionSet distributionSetEntity, final List targetEntities) { @@ -325,8 +329,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl targetEntitiesIdsChunks.forEach(this::cancelInactiveScheduledActionsForTargets); setAssignedDistributionSetAndTargetUpdateStatus(assignmentStrategy, distributionSetEntity, targetEntitiesIdsChunks); - final List assignedActions = createActions(targetsWithActionType, targetEntities, assignmentStrategy, - distributionSetEntity); + final List assignedActions = createActions(initiatedBy, targetsWithActionType, targetEntities, + assignmentStrategy, distributionSetEntity); // create initial action status when action is created so we remember // the initial running status because we will change the status // of the action itself and with this action status we have a nicer @@ -417,12 +421,15 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl assignmentStrategy.setAssignedDistributionSetAndTargetStatus(set, targetIdsChunks, currentUser); } - private List createActions(final Collection targetsWithActionType, - final List targets, final AbstractDsAssignmentStrategy assignmentStrategy, - final JpaDistributionSet set) { + private List createActions(final String initiatedBy, + final Collection targetsWithActionType, final List targets, + final AbstractDsAssignmentStrategy assignmentStrategy, final JpaDistributionSet set) { - return targetsWithActionType.stream().map(twt -> assignmentStrategy.createTargetAction(twt, targets, set)) - .filter(Objects::nonNull).map(actionRepository::save).collect(Collectors.toList()); + return targetsWithActionType.stream() + .map(twt -> assignmentStrategy.createTargetAction(initiatedBy, twt, targets, set)) + .filter(Objects::nonNull) + .map(actionRepository::save) + .collect(Collectors.toList()); } private void createActionsStatus(final Collection actions, diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java index 6d2134542..78ee099ee 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java @@ -624,6 +624,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement { action.setStatus(Status.SCHEDULED); action.setRollout(rollout); action.setRolloutGroup(rolloutGroup); + action.setInitiatedBy(rollout.getCreatedBy()); rollout.getWeight().ifPresent(action::setWeight); actionRepository.save(action); }); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java index b05e5f221..db48b936b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java @@ -35,13 +35,13 @@ import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper; -import org.eclipse.hawkbit.repository.jpa.utils.TenantConfigHelper; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -74,6 +74,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme private final QuotaManagement quotaManagement; private final TenantConfigurationManagement tenantConfigurationManagement; private final SystemSecurityContext systemSecurityContext; + private final TenantAware tenantAware; private final Database database; @@ -81,7 +82,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme final TargetRepository targetRepository, final VirtualPropertyReplacer virtualPropertyReplacer, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final Database database, final TenantConfigurationManagement tenantConfigurationManagement, - final SystemSecurityContext systemSecurityContext) { + final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware) { this.targetFilterQueryRepository = targetFilterQueryRepository; this.targetRepository = targetRepository; this.virtualPropertyReplacer = virtualPropertyReplacer; @@ -90,6 +91,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme this.database = database; this.tenantConfigurationManagement = tenantConfigurationManagement; this.systemSecurityContext = systemSecurityContext; + this.tenantAware = tenantAware; } @Override @@ -240,6 +242,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme targetFilterQuery.setAutoAssignDistributionSet(null); targetFilterQuery.setAutoAssignActionType(null); targetFilterQuery.setAutoAssignWeight(null); + targetFilterQuery.setAutoAssignInitiatedBy(null); } else { WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(update); // we cannot be sure that the quota was enforced at creation time @@ -250,6 +253,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme final JpaDistributionSet ds = findDistributionSetAndThrowExceptionIfNotFound(update.getDsId()); verifyDistributionSetAndThrowExceptionIfNotValid(ds); targetFilterQuery.setAutoAssignDistributionSet(ds); + targetFilterQuery.setAutoAssignInitiatedBy(tenantAware.getCurrentUsername()); targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType())); targetFilterQuery.setAutoAssignWeight(update.getWeight()); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OfflineDsAssignmentStrategy.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OfflineDsAssignmentStrategy.java index f444c92fb..ee3042df1 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OfflineDsAssignmentStrategy.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OfflineDsAssignmentStrategy.java @@ -90,9 +90,9 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy { } @Override - protected JpaAction createTargetAction(final TargetWithActionType targetWithActionType, + protected JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType, final List targets, final JpaDistributionSet set) { - final JpaAction result = super.createTargetAction(targetWithActionType, targets, set); + final JpaAction result = super.createTargetAction(initiatedBy, targetWithActionType, targets, set); if (result != null) { result.setStatus(Status.FINISHED); result.setActive(Boolean.FALSE); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OnlineDsAssignmentStrategy.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OnlineDsAssignmentStrategy.java index f008084d5..8a3ab91e3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OnlineDsAssignmentStrategy.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OnlineDsAssignmentStrategy.java @@ -127,9 +127,9 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy { } @Override - JpaAction createTargetAction(final TargetWithActionType targetWithActionType, final List targets, - final JpaDistributionSet set) { - final JpaAction result = super.createTargetAction(targetWithActionType, targets, set); + JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType, + final List targets, final JpaDistributionSet set) { + final JpaAction result = super.createTargetAction(initiatedBy, targetWithActionType, targets, set); if (result != null) { result.setStatus(Status.RUNNING); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java index 7e93673be..91e36f5c0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java @@ -533,6 +533,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * to access quotas * @param properties * JPA properties + * @param tenantAware + * the {@link TenantAware} bean holding the tenant information * * @return a new {@link TargetFilterQueryManagement} */ @@ -543,10 +545,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { final VirtualPropertyReplacer virtualPropertyReplacer, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final JpaProperties properties, final TenantConfigurationManagement tenantConfigurationManagement, - final SystemSecurityContext systemSecurityContext) { + final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware) { return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, targetRepository, virtualPropertyReplacer, distributionSetManagement, quotaManagement, properties.getDatabase(), - tenantConfigurationManagement, systemSecurityContext); + tenantConfigurationManagement, systemSecurityContext, tenantAware); } /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java index 1f2fb70cd..eb832b13a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java @@ -32,6 +32,7 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; /** * Checks if targets need a new distribution set (DS) based on the target filter @@ -146,12 +147,19 @@ public class AutoAssignChecker implements AutoAssignExecutor { targetFilterQuery.getAutoAssignWeight().orElse(null), PAGE_SIZE); final int count = deploymentRequests.size(); if (count > 0) { - deploymentManagement.assignDistributionSets(deploymentRequests, actionMessage); + deploymentManagement.assignDistributionSets(getAutoAssignmentInitiatedBy(targetFilterQuery), + deploymentRequests, actionMessage); } return count; }); } + private static String getAutoAssignmentInitiatedBy(final TargetFilterQuery targetFilterQuery) { + return StringUtils.isEmpty(targetFilterQuery.getAutoAssignInitiatedBy()) ? + targetFilterQuery.getCreatedBy() : + targetFilterQuery.getAutoAssignInitiatedBy(); + } + /** * Gets all matching targets with the designated action from the target * management diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java index 2585705de..17be497a9 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java @@ -36,6 +36,7 @@ import org.springframework.security.core.context.SecurityContextHolder; @EntityListeners({ AuditingEntityListener.class, EntityPropertyChangeListener.class, EntityInterceptorListener.class }) public abstract class AbstractJpaBaseEntity implements BaseEntity { private static final long serialVersionUID = 1L; + protected static final int USERNAME_FIELD_LENGTH = 64; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -67,7 +68,7 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity { @Override @Access(AccessType.PROPERTY) - @Column(name = "created_by", insertable = true, updatable = false, nullable = false, length = 64) + @Column(name = "created_by", insertable = true, updatable = false, nullable = false, length = USERNAME_FIELD_LENGTH) public String getCreatedBy() { return createdBy; } @@ -81,7 +82,7 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity { @Override @Access(AccessType.PROPERTY) - @Column(name = "last_modified_by", insertable = true, updatable = true, nullable = false, length = 64) + @Column(name = "last_modified_by", insertable = true, updatable = true, nullable = false, length = USERNAME_FIELD_LENGTH) public String getLastModifiedBy() { return lastModifiedBy; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java index 6879f0a66..2b91edce3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java @@ -137,6 +137,9 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio @Column(name = "external_ref", length = Action.EXTERNAL_REF_MAX_LENGTH) private String externalRef; + @Column(name = "initiated_by", updatable = false, nullable = false, length = USERNAME_FIELD_LENGTH) + private String initiatedBy; + @Override public DistributionSet getDistributionSet() { return distributionSet; @@ -363,4 +366,13 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio public String getExternalRef() { return externalRef; } + + public void setInitiatedBy(final String initiatedBy) { + this.initiatedBy = initiatedBy; + } + + @Override + public String getInitiatedBy() { + return initiatedBy; + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java index af945a327..eea5468c9 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java @@ -77,6 +77,9 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity @Column(name = "auto_assign_weight", nullable = true) private Integer autoAssignWeight; + @Column(name = "auto_assign_initiated_by", nullable = true, length = USERNAME_FIELD_LENGTH) + private String autoAssignInitiatedBy; + public JpaTargetFilterQuery() { // Default constructor for JPA. } @@ -149,6 +152,14 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity this.autoAssignWeight = weight; } + public String getAutoAssignInitiatedBy() { + return autoAssignInitiatedBy; + } + + public void setAutoAssignInitiatedBy(final String autoAssignInitiatedBy) { + this.autoAssignInitiatedBy = autoAssignInitiatedBy; + } + @Override public void fireCreateEvent(final DescriptorEvent descriptorEvent) { EventPublisherHolder.getInstance().getEventPublisher().publishEvent( diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_16__add_action_initiated_by___DB2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_16__add_action_initiated_by___DB2.sql new file mode 100644 index 000000000..d0b559b61 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_16__add_action_initiated_by___DB2.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_action ADD COLUMN initiated_by VARCHAR(64) NOT NULL; +ALTER TABLE sp_target_filter_query ADD COLUMN auto_assign_initiated_by VARCHAR(64); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_16__add_action_initiated_by___H2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_16__add_action_initiated_by___H2.sql new file mode 100644 index 000000000..d0b559b61 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_16__add_action_initiated_by___H2.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_action ADD COLUMN initiated_by VARCHAR(64) NOT NULL; +ALTER TABLE sp_target_filter_query ADD COLUMN auto_assign_initiated_by VARCHAR(64); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_16__add_action_initiated_by___MYSQL.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_16__add_action_initiated_by___MYSQL.sql new file mode 100644 index 000000000..d0b559b61 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_16__add_action_initiated_by___MYSQL.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_action ADD COLUMN initiated_by VARCHAR(64) NOT NULL; +ALTER TABLE sp_target_filter_query ADD COLUMN auto_assign_initiated_by VARCHAR(64); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/POSTGRESQL/V1_12_16__add_action_initiated_by___POSTGRESQL.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/POSTGRESQL/V1_12_16__add_action_initiated_by___POSTGRESQL.sql new file mode 100644 index 000000000..f0d49e8c9 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/POSTGRESQL/V1_12_16__add_action_initiated_by___POSTGRESQL.sql @@ -0,0 +1,5 @@ +ALTER TABLE sp_action +ADD COLUMN initiated_by VARCHAR (64) NOT NULL; + +ALTER TABLE sp_target_filter_query +ADD COLUMN auto_assign_initiated_by VARCHAR (64); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_16__add_action_initiated_by___SQL_SERVER.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_16__add_action_initiated_by___SQL_SERVER.sql new file mode 100644 index 000000000..9b4290a9d --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_16__add_action_initiated_by___SQL_SERVER.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_action ADD initiated_by VARCHAR(64) NOT NULL; +ALTER TABLE sp_target_filter_query ADD auto_assign_initiated_by VARCHAR(64); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java index e622e8afe..a089c6dce 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java @@ -104,6 +104,7 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest { generateAction.setTarget(testdataFactory.createTarget("Test")); generateAction.setDistributionSet(dsA); generateAction.setStatus(Status.RUNNING); + generateAction.setInitiatedBy(tenantAware.getCurrentUsername()); final Action action = actionRepository.save(generateAction); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionEventTest.java index 57c8953a7..a32fb50b4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionEventTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionEventTest.java @@ -84,6 +84,7 @@ public class ActionEventTest extends AbstractRemoteEntityEventTest { generateAction.setTarget(target); generateAction.setDistributionSet(distributionSet); generateAction.setStatus(Status.RUNNING); + generateAction.setInitiatedBy(tenantAware.getCurrentUsername()); return actionRepository.save(generateAction); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java index 90bcc5e6f..b6b637261 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java @@ -480,7 +480,9 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest { assertThat(actionRepository.count()).isEqualTo(20); assertThat(actionRepository.findByDistributionSetId(PAGE, ds.getId())).as("Offline actions are not active") - .allMatch(action -> !action.isActive()); + .allMatch(action -> !action.isActive()) + .as("Actions should be initiated by current user") + .allMatch(a -> a.getInitiatedBy().equals(tenantAware.getCurrentUsername())); assertThat(targetManagement.findByInstalledDistributionSet(PAGE, ds.getId()).getContent()) .usingElementComparator(controllerIdComparator()).containsAll(targets).hasSize(10) @@ -514,6 +516,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest { assertThat(getResultingActionCount(assignmentResults)).isEqualTo(4); targetIds.forEach(controllerId -> { final List assignedDsIds = actionRepository.findByTargetControllerId(PAGE, controllerId).stream() + .peek(a -> assertThat(a.getInitiatedBy()).as("Actions should be initiated by current user") + .isEqualTo(tenantAware.getCurrentUsername())) .map(action -> action.getDistributionSet().getId()).collect(Collectors.toList()); assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds); }); @@ -587,10 +591,12 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest { private void assertDsExclusivelyAssignedToTargets(final List targets, final long dsId, final boolean active, final Status status) { final List assignment = actionRepository.findByDistributionSetId(PAGE, dsId).getContent(); + final String currentUsername = tenantAware.getCurrentUsername(); assertThat(assignment).hasSize(10).allMatch(action -> action.isActive() == active) .as("Is assigned to DS " + dsId).allMatch(action -> action.getDistributionSet().getId().equals(dsId)) - .as("State is " + status).allMatch(action -> action.getStatus() == status); + .as("State is " + status).allMatch(action -> action.getStatus() == status) + .as("Initiated by " + currentUsername).allMatch(a -> a.getInitiatedBy().equals(currentUsername)); final long[] targetIds = targets.stream().mapToLong(Target::getId).toArray(); assertThat(targetIds).as("All targets represented in assignment").containsExactlyInAnyOrder( assignment.stream().mapToLong(action -> action.getTarget().getId()).toArray()); @@ -617,7 +623,10 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest { final List dsIds = distributionSets.stream().map(DistributionSet::getId).collect(Collectors.toList()); targets.forEach(target -> { final List assignedDsIds = actionRepository.findByTargetControllerId(PAGE, target.getControllerId()) - .stream().map(action -> action.getDistributionSet().getId()).collect(Collectors.toList()); + .stream() + .peek(a -> assertThat(a.getInitiatedBy()).as("Initiated by current user") + .isEqualTo(tenantAware.getCurrentUsername())) + .map(action -> action.getDistributionSet().getId()).collect(Collectors.toList()); assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds); }); } @@ -647,6 +656,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest { targets.forEach(target -> { actionRepository.findByTargetControllerId(PAGE, target.getControllerId()).forEach(action -> { assertThat(action.getDistributionSet().getId()).isIn(dsIds); + assertThat(action.getInitiatedBy()).as("Should be Initiated by current user") + .isEqualTo(tenantAware.getCurrentUsername()); deploymentManagement.cancelAction(action.getId()); }); }); @@ -851,7 +862,10 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest { assignDistributionSet(ds, savedDeployedTargets); // verify that one Action for each assignDistributionSet - assertThat(actionRepository.findAll(PAGE).getNumberOfElements()).as("wrong size of actions").isEqualTo(20); + final Page actions = actionRepository.findAll(PAGE); + assertThat(actions.getNumberOfElements()).as("wrong size of actions").isEqualTo(20); + assertThat(actions).as("Actions should be initiated by current user") + .allMatch(a -> a.getInitiatedBy().equals(tenantAware.getCurrentUsername())); final Iterable allFoundTargets = targetManagement.findAll(PAGE).getContent(); @@ -945,6 +959,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest { // retrieving all Actions created by the assignDistributionSet call final Page page = actionRepository.findAll(PAGE); + assertThat(page).as("Actions should be initiated by current user") + .allMatch(a -> a.getInitiatedBy().equals(tenantAware.getCurrentUsername())); // and verify the number assertThat(page.getTotalElements()).as("wrong size of actions") .isEqualTo(noOfDeployedTargets * noOfDistributionSets); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java index 02eea953a..59183de64 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java @@ -252,6 +252,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { // running final List runningActions = findActionsByRolloutAndStatus(createdRollout, Status.RUNNING); assertThat(runningActions).hasSize(amountTargetsForRollout / amountGroups); + assertThat(runningActions).as("Created actions are initiated by rollout creator") + .allMatch(a -> a.getInitiatedBy().equals(createdRollout.getCreatedBy())); // the rest targets are only scheduled assertThat(findActionsByRolloutAndStatus(createdRollout, Status.SCHEDULED)) .hasSize(amountTargetsForRollout - (amountTargetsForRollout / amountGroups)); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java index 9327c15ea..9349fcb11 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java @@ -12,10 +12,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.jpa.ActionRepository; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -26,6 +28,7 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import io.qameta.allure.Description; @@ -44,6 +47,9 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest { @Autowired private AutoAssignChecker autoAssignChecker; + @Autowired + private ActionRepository actionRepository; + @Test @Description("Verifies that a running action is auto canceled by a AutoAssignment which assigns another distribution-set.") public void autoAssignDistributionSetAndAutoCloseOldActions() { @@ -99,10 +105,11 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest { final DistributionSet setB = testdataFactory.createDistributionSet("dsB"); // target filter query that matches all targets - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement - .create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")); - targetFilterQueryManagement.updateAutoAssignDS( - entityFactory.targetFilterQuery().updateAutoAssign(targetFilterQuery.getId()).ds(setA.getId())); + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.updateAutoAssignDS( + entityFactory.targetFilterQuery() + .updateAutoAssign(targetFilterQueryManagement.create( + entityFactory.targetFilterQuery().create().name("filterA").query("name==*")).getId()) + .ds(setA.getId())); final String targetDsAIdPref = "targ"; final List targets = testdataFactory.createTargets(100, targetDsAIdPref, @@ -134,6 +141,7 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest { // first 5 should keep their dsB, because they already had the dsA once verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(0, 5), targetsCount); + verifyThatCreatedActionsAreInitiatedByCurrentUser(targetFilterQuery, setA, targets); } @Test @@ -208,6 +216,18 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest { } + @Step + private void verifyThatCreatedActionsAreInitiatedByCurrentUser(final TargetFilterQuery targetFilterQuery, + final DistributionSet distributionSet, final List targets) { + final Set targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toSet()); + + actionRepository.findByDistributionSetId(Pageable.unpaged(), distributionSet.getId()) + .stream().filter(a -> targetIds.contains(a.getTarget().getControllerId())) + .forEach(a -> assertThat(a.getInitiatedBy()).as( + "Action should be initiated by the user who initiated the auto assignment") + .isEqualTo(targetFilterQuery.getAutoAssignInitiatedBy())); + } + @Test @Description("Test auto assignment of a distribution set with FORCED, SOFT and DOWNLOAD_ONLY action types") public void checkAutoAssignWithDifferentActionTypes() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLActionFieldsTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLActionFieldsTest.java index de3918da4..91810a190 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLActionFieldsTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLActionFieldsTest.java @@ -47,6 +47,7 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest { action.setTarget(target); action.setStatus(Status.RUNNING); action.setWeight(45); + action.setInitiatedBy(tenantAware.getCurrentUsername()); target.addAction(action); actionRepository.save(action); @@ -58,6 +59,7 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest { newAction.setStatus(Status.RUNNING); newAction.setTarget(target); newAction.setWeight(45); + newAction.setInitiatedBy(tenantAware.getCurrentUsername()); actionRepository.save(newAction); target.addAction(newAction); } diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java index eadf04a46..e378c2f09 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java @@ -46,6 +46,18 @@ public class SecurityContextTenantAware implements TenantAware { return null; } + @Override + public String getCurrentUsername() { + final SecurityContext context = SecurityContextHolder.getContext(); + if (context.getAuthentication() != null) { + final Object principal = context.getAuthentication().getPrincipal(); + if (principal instanceof UserPrincipal) { + return ((UserPrincipal) principal).getUsername(); + } + } + return null; + } + @Override public T runAsTenant(final String tenant, final TenantRunner callable) { final SecurityContext originalContext = SecurityContextHolder.getContext();