diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java index 5f2bb062c2ec..0aaf3ed8f6c0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java @@ -121,6 +121,9 @@ Optional findWithTemplateAndSolutionParticipationTeamAssign @EntityGraph(type = LOAD, attributePaths = "submissionPolicy") List findWithSubmissionPolicyByProjectKey(String projectKey); + @EntityGraph(type = LOAD, attributePaths = { "buildConfig" }) + Optional findWithBuildConfigById(long exerciseId); + /** * Finds one programming exercise including its submission policy by the exercise's project key. * @@ -743,6 +746,18 @@ default ProgrammingExercise findForCreationByIdElseThrow(long programmingExercis return getValueElseThrow(findForCreationById(programmingExerciseId), programmingExerciseId); } + /** + * Find a programming exercise by its id, with eagerly loaded build config. + * + * @param programmingExerciseId of the programming exercise. + * @return The programming exercise related to the given id + * @throws EntityNotFoundException the programming exercise could not be found. + */ + @NotNull + default ProgrammingExercise findByIdWithBuildConfigElseThrow(long programmingExerciseId) throws EntityNotFoundException { + return getValueElseThrow(findWithBuildConfigById(programmingExerciseId), programmingExerciseId); + } + /** * Saves the given programming exercise to the database. *

diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIService.java index 9292031adba5..e0ec98ed52cf 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIService.java @@ -3,18 +3,25 @@ import static de.tum.cit.aet.artemis.core.config.Constants.NEW_RESULT_RESOURCE_API_PATH; import java.net.URL; +import java.time.ZonedDateTime; import java.util.Comparator; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Supplier; +import org.gitlab4j.api.Constants; import org.gitlab4j.api.GitLabApi; import org.gitlab4j.api.GitLabApiException; +import org.gitlab4j.api.GroupApi; import org.gitlab4j.api.ProjectApi; +import org.gitlab4j.api.models.AccessLevel; import org.gitlab4j.api.models.Pipeline; import org.gitlab4j.api.models.PipelineFilter; import org.gitlab4j.api.models.PipelineStatus; import org.gitlab4j.api.models.Project; +import org.gitlab4j.api.models.ProjectAccessToken; import org.gitlab4j.api.models.Variable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +44,7 @@ import de.tum.cit.aet.artemis.programming.dto.CheckoutDirectoriesDTO; import de.tum.cit.aet.artemis.programming.repository.BuildPlanRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseBuildConfigRepository; +import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.service.UriService; import de.tum.cit.aet.artemis.programming.service.ci.AbstractContinuousIntegrationService; import de.tum.cit.aet.artemis.programming.service.ci.CIPermission; @@ -51,6 +59,8 @@ public class GitLabCIService extends AbstractContinuousIntegrationService { private static final String GITLAB_CI_FILE_EXTENSION = ".yml"; + private static final String GITLAB_TEST_TOKEN_NAME = "Artemis Test Token"; + private static final Logger log = LoggerFactory.getLogger(GitLabCIService.class); private static final String VARIABLE_BUILD_DOCKER_IMAGE_NAME = "ARTEMIS_BUILD_DOCKER_IMAGE"; @@ -91,6 +101,8 @@ public class GitLabCIService extends AbstractContinuousIntegrationService { private final ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository; + private final ProgrammingExerciseRepository programmingExerciseRepository; + @Value("${artemis.version-control.url}") private URL gitlabServerUrl; @@ -110,29 +122,30 @@ public class GitLabCIService extends AbstractContinuousIntegrationService { private String gitlabToken; public GitLabCIService(GitLabApi gitlab, UriService uriService, BuildPlanRepository buildPlanRepository, GitLabCIBuildPlanService buildPlanService, - ProgrammingLanguageConfiguration programmingLanguageConfiguration, ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository) { + ProgrammingLanguageConfiguration programmingLanguageConfiguration, ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository, + ProgrammingExerciseRepository programmingExerciseRepository) { this.gitlab = gitlab; this.uriService = uriService; this.buildPlanRepository = buildPlanRepository; this.buildPlanService = buildPlanService; this.programmingLanguageConfiguration = programmingLanguageConfiguration; this.programmingExerciseBuildConfigRepository = programmingExerciseBuildConfigRepository; + this.programmingExerciseRepository = programmingExerciseRepository; } @Override public void createBuildPlanForExercise(ProgrammingExercise exercise, String planKey, VcsRepositoryUri repositoryUri, VcsRepositoryUri testRepositoryUri, VcsRepositoryUri solutionRepositoryUri) { - addBuildPlanToProgrammingExerciseIfUnset(exercise); - setupGitLabCIConfiguration(repositoryUri, exercise, generateBuildPlanId(exercise.getProjectKey(), planKey)); - // TODO: triggerBuild(repositoryUri, exercise.getBranch()); + addBuildPlanToProgrammingExercise(exercise, false); + // This method is called twice when creating an exercise. Once for the template repository and once for the solution repository. + // The second time, we don't want to overwrite the configuration. + setupGitLabCIConfigurationForGroup(exercise, false); + setupGitLabCIConfigurationForRepository(repositoryUri, exercise, generateBuildPlanId(exercise.getProjectKey(), planKey)); } - private void setupGitLabCIConfiguration(VcsRepositoryUri repositoryUri, ProgrammingExercise exercise, String buildPlanId) { + private void setupGitLabCIConfigurationForRepository(VcsRepositoryUri repositoryUri, ProgrammingExercise exercise, String buildPlanId) { final String repositoryPath = uriService.getRepositoryPathFromRepositoryUri(repositoryUri); - ProjectApi projectApi = gitlab.getProjectApi(); - - programmingExerciseBuildConfigRepository.loadAndSetBuildConfig(exercise); - + final ProjectApi projectApi = gitlab.getProjectApi(); try { Project project = projectApi.getProject(repositoryPath); @@ -144,40 +157,99 @@ private void setupGitLabCIConfiguration(VcsRepositoryUri repositoryUri, Programm project.setCiConfigPath(buildPlanUrl); projectApi.updateProject(project); + + setRepositoryVariableIfUnset(repositoryPath, VARIABLE_BUILD_PLAN_ID_NAME, buildPlanId); } catch (GitLabApiException e) { throw new GitLabCIException("Error enabling CI for " + repositoryUri, e); } + } + + private void setupGitLabCIConfigurationForGroup(ProgrammingExercise exercise, boolean overwrite) { + programmingExerciseBuildConfigRepository.loadAndSetBuildConfig(exercise); + + final String projectKey = exercise.getProjectKey(); + final ProgrammingExerciseBuildConfig buildConfig = exercise.getBuildConfig(); + + updateGroupVariable(projectKey, VARIABLE_BUILD_DOCKER_IMAGE_NAME, + programmingLanguageConfiguration.getImage(exercise.getProgrammingLanguage(), Optional.ofNullable(exercise.getProjectType())), overwrite); + updateGroupVariable(projectKey, VARIABLE_BUILD_LOGS_FILE_NAME, "build.log", overwrite); + // TODO: Implement the custom feedback feature + updateGroupVariable(projectKey, VARIABLE_CUSTOM_FEEDBACK_DIR_NAME, "TODO", overwrite); + updateGroupVariable(projectKey, VARIABLE_NOTIFICATION_PLUGIN_DOCKER_IMAGE_NAME, notificationPluginDockerImage, overwrite); + updateGroupVariable(projectKey, VARIABLE_NOTIFICATION_SECRET_NAME, artemisAuthenticationTokenValue, overwrite); + updateGroupVariable(projectKey, VARIABLE_NOTIFICATION_URL_NAME, artemisServerUrl.toExternalForm() + NEW_RESULT_RESOURCE_API_PATH, overwrite); + updateGroupVariable(projectKey, VARIABLE_SUBMISSION_GIT_BRANCH_NAME, buildConfig.getBranch(), overwrite); + updateGroupVariable(projectKey, VARIABLE_TEST_GIT_BRANCH_NAME, buildConfig.getBranch(), overwrite); + updateGroupVariable(projectKey, VARIABLE_TEST_GIT_REPOSITORY_SLUG_NAME, uriService.getRepositorySlugFromRepositoryUriString(exercise.getTestRepositoryUri()), overwrite); + updateGroupVariable(projectKey, VARIABLE_TEST_GIT_TOKEN, () -> generateGitLabTestToken(exercise), overwrite); + updateGroupVariable(projectKey, VARIABLE_TEST_GIT_USER, gitlabUser, overwrite); + updateGroupVariable(projectKey, VARIABLE_TEST_RESULTS_DIR_NAME, "target/surefire-reports", overwrite); + } + + private void updateGroupVariable(String projectKey, String key, String value, boolean overwrite) { + updateGroupVariable(projectKey, key, () -> value, overwrite); + } + + private void updateGroupVariable(String projectKey, String key, Supplier value, boolean overwrite) { + final GroupApi groupApi = gitlab.getGroupApi(); + if (groupApi.getOptionalVariable(projectKey, key).isEmpty()) { + try { + String valueString = value.get(); + groupApi.createVariable(projectKey, key, valueString, false, canBeMasked(valueString)); + } + catch (GitLabApiException e) { + log.error("Error creating variable '{}' for group {}", key, projectKey, e); + throw new GitLabCIException("Error creating variable '" + key + "' for group " + projectKey, e); + } + } + else if (overwrite) { + try { + String valueString = value.get(); + groupApi.updateVariable(projectKey, key, valueString, false, canBeMasked(valueString)); + } + catch (GitLabApiException e) { + log.error("Error updating variable '{}' for group {}", key, projectKey, e); + throw new GitLabCIException("Error updating variable '" + key + "' for group " + projectKey, e); + } + } + } + + private String generateGitLabTestToken(ProgrammingExercise programmingExercise) { + String testRepositoryPath = uriService.getRepositoryPathFromRepositoryUri(programmingExercise.getVcsTestRepositoryUri()); + ZonedDateTime courseEndDate = programmingExercise.getCourseViaExerciseGroupOrCourseMember().getEndDate(); + + Date expiryDate; + if (courseEndDate != null && courseEndDate.isAfter(ZonedDateTime.now())) { + expiryDate = Date.from(courseEndDate.toInstant()); + } + else { + expiryDate = Date.from(ZonedDateTime.now().plusMonths(6).toInstant()); + } + ProjectAccessToken projectAccessToken; try { - // TODO: Reduce the number of API calls - ProgrammingExerciseBuildConfig buildConfig = exercise.getBuildConfig(); - updateVariable(repositoryPath, VARIABLE_BUILD_DOCKER_IMAGE_NAME, - programmingLanguageConfiguration.getImage(exercise.getProgrammingLanguage(), Optional.ofNullable(exercise.getProjectType()))); - updateVariable(repositoryPath, VARIABLE_BUILD_LOGS_FILE_NAME, "build.log"); - updateVariable(repositoryPath, VARIABLE_BUILD_PLAN_ID_NAME, buildPlanId); - // TODO: Implement the custom feedback feature - updateVariable(repositoryPath, VARIABLE_CUSTOM_FEEDBACK_DIR_NAME, "TODO"); - updateVariable(repositoryPath, VARIABLE_NOTIFICATION_PLUGIN_DOCKER_IMAGE_NAME, notificationPluginDockerImage); - updateVariable(repositoryPath, VARIABLE_NOTIFICATION_SECRET_NAME, artemisAuthenticationTokenValue); - updateVariable(repositoryPath, VARIABLE_NOTIFICATION_URL_NAME, artemisServerUrl.toExternalForm() + NEW_RESULT_RESOURCE_API_PATH); - updateVariable(repositoryPath, VARIABLE_SUBMISSION_GIT_BRANCH_NAME, buildConfig.getBranch()); - updateVariable(repositoryPath, VARIABLE_TEST_GIT_BRANCH_NAME, buildConfig.getBranch()); - updateVariable(repositoryPath, VARIABLE_TEST_GIT_REPOSITORY_SLUG_NAME, uriService.getRepositorySlugFromRepositoryUriString(exercise.getTestRepositoryUri())); - // TODO: Use a token that is only valid for the test repository for each programming exercise - updateVariable(repositoryPath, VARIABLE_TEST_GIT_TOKEN, gitlabToken); - updateVariable(repositoryPath, VARIABLE_TEST_GIT_USER, gitlabUser); - updateVariable(repositoryPath, VARIABLE_TEST_RESULTS_DIR_NAME, "target/surefire-reports"); + projectAccessToken = gitlab.getProjectApi().createProjectAccessToken(testRepositoryPath, GITLAB_TEST_TOKEN_NAME, + List.of(Constants.ProjectAccessTokenScope.READ_REPOSITORY), expiryDate, Long.valueOf(AccessLevel.REPORTER.value)); } catch (GitLabApiException e) { - log.error("Error creating variable for {} The variables may already have been created.", repositoryUri, e); + log.error("Error creating project access token for test repository {}", testRepositoryPath, e); + throw new GitLabCIException("Error creating project access token for test repository " + testRepositoryPath, e); } + return projectAccessToken.getToken(); } - private void updateVariable(String repositoryPath, String key, String value) throws GitLabApiException { - // TODO: We can even define the variables on group level - // TODO: If the variable already exists, we should update it - gitlab.getProjectApi().createVariable(repositoryPath, key, value, Variable.Type.ENV_VAR, false, canBeMasked(value)); + private void setRepositoryVariableIfUnset(String repositoryPath, String key, String value) { + final ProjectApi projectApi = gitlab.getProjectApi(); + if (projectApi.getOptionalVariable(repositoryPath, key).isEmpty()) { + try { + projectApi.createVariable(repositoryPath, key, value, Variable.Type.ENV_VAR, false, canBeMasked(value)); + } + catch (GitLabApiException e) { + log.error("Error creating variable '{}' for repository {}", key, repositoryPath, e); + throw new GitLabCIException("Error creating variable '" + key + "' for repository " + repositoryPath, e); + } + } } private boolean canBeMasked(String value) { @@ -185,9 +257,9 @@ private boolean canBeMasked(String value) { return value != null && value.matches("^[a-zA-Z0-9+/=@:.~]{8,}$"); } - private void addBuildPlanToProgrammingExerciseIfUnset(ProgrammingExercise programmingExercise) { + private void addBuildPlanToProgrammingExercise(ProgrammingExercise programmingExercise, boolean overwrite) { Optional optionalBuildPlan = buildPlanRepository.findByProgrammingExercises_IdWithProgrammingExercises(programmingExercise.getId()); - if (optionalBuildPlan.isEmpty()) { + if (optionalBuildPlan.isEmpty() || overwrite) { var defaultBuildPlan = buildPlanService.generateDefaultBuildPlan(programmingExercise); buildPlanRepository.setBuildPlanForExercise(defaultBuildPlan, programmingExercise); } @@ -195,15 +267,15 @@ private void addBuildPlanToProgrammingExerciseIfUnset(ProgrammingExercise progra @Override public void recreateBuildPlansForExercise(ProgrammingExercise exercise) { - addBuildPlanToProgrammingExerciseIfUnset(exercise); + addBuildPlanToProgrammingExercise(exercise, true); + // When recreating the build plan for the exercise, we want to overwrite the configuration. + setupGitLabCIConfigurationForGroup(exercise, true); - VcsRepositoryUri templateUrl = exercise.getVcsTemplateRepositoryUri(); - setupGitLabCIConfiguration(templateUrl, exercise, exercise.getTemplateBuildPlanId()); - // TODO: triggerBuild(templateUrl, exercise.getBranch()); + VcsRepositoryUri templateUri = exercise.getVcsTemplateRepositoryUri(); + setupGitLabCIConfigurationForRepository(templateUri, exercise, exercise.getTemplateBuildPlanId()); - VcsRepositoryUri solutionUrl = exercise.getVcsSolutionRepositoryUri(); - setupGitLabCIConfiguration(solutionUrl, exercise, exercise.getSolutionBuildPlanId()); - // TODO: triggerBuild(solutionUrl, exercise.getBranch()); + VcsRepositoryUri solutionUri = exercise.getVcsSolutionRepositoryUri(); + setupGitLabCIConfigurationForRepository(solutionUri, exercise, exercise.getSolutionBuildPlanId()); } @Override @@ -223,19 +295,20 @@ private String generateBuildPlanId(String projectKey, String planKey) { @Override public void configureBuildPlan(ProgrammingExerciseParticipation participation, String defaultBranch) { - setupGitLabCIConfiguration(participation.getVcsRepositoryUri(), participation.getProgrammingExercise(), participation.getBuildPlanId()); + ProgrammingExercise programmingExercise = programmingExerciseRepository.findByIdWithBuildConfigElseThrow(participation.getProgrammingExercise().getId()); + setupGitLabCIConfigurationForRepository(participation.getVcsRepositoryUri(), programmingExercise, participation.getBuildPlanId()); } @Override public void deleteProject(String projectKey) { - log.error("Unsupported action: GitLabCIService.deleteBuildPlan()"); - log.error("Please refer to the repository for deleting the project. The build plan can not be deleted separately."); + log.debug("Unsupported action: GitLabCIService.deleteBuildPlan()"); + log.debug("Please refer to the repository for deleting the project. The build plan can not be deleted separately."); } @Override public void deleteBuildPlan(String projectKey, String buildPlanId) { - log.error("Unsupported action: GitLabCIService.deleteBuildPlan()"); - log.error("Please refer to the repository for deleting the project. The build plan can not be deleted separately."); + log.debug("Unsupported action: GitLabCIService.deleteBuildPlan()"); + log.debug("Please refer to the repository for deleting the project. The build plan can not be deleted separately."); } @Override @@ -278,46 +351,46 @@ private Optional getLatestPipeline(final ProgrammingExerciseParticipat @Override public boolean checkIfBuildPlanExists(String projectKey, String buildPlanId) { - log.error("Unsupported action: GitLabCIService.checkIfBuildPlanExists()"); + log.debug("Unsupported action: GitLabCIService.checkIfBuildPlanExists()"); return true; } @Override public ResponseEntity retrieveLatestArtifact(ProgrammingExerciseParticipation participation) { - log.error("Unsupported action: GitLabCIService.retrieveLatestArtifact()"); + log.debug("Unsupported action: GitLabCIService.retrieveLatestArtifact()"); return null; } @Override public String checkIfProjectExists(String projectKey, String projectName) { - log.error("Unsupported action: GitLabCIService.checkIfProjectExists()"); + log.debug("Unsupported action: GitLabCIService.checkIfProjectExists()"); return null; } @Override public void enablePlan(String projectKey, String planKey) { - log.error("Unsupported action: GitLabCIService.enablePlan()"); + log.debug("Unsupported action: GitLabCIService.enablePlan()"); } @Override public void updatePlanRepository(String buildProjectKey, String buildPlanKey, String ciRepoName, String repoProjectKey, String newRepoUri, String existingRepoUri, String newDefaultBranch) { - log.error("Unsupported action: GitLabCIService.updatePlanRepository()"); + log.debug("Unsupported action: GitLabCIService.updatePlanRepository()"); } @Override public void giveProjectPermissions(String projectKey, List groups, List permissions) { - log.error("Unsupported action: GitLabCIService.giveProjectPermissions()"); + log.debug("Unsupported action: GitLabCIService.giveProjectPermissions()"); } @Override public void givePlanPermissions(ProgrammingExercise programmingExercise, String planName) { - log.error("Unsupported action: GitLabCIService.givePlanPermissions()"); + log.debug("Unsupported action: GitLabCIService.givePlanPermissions()"); } @Override public void removeAllDefaultProjectPermissions(String projectKey) { - log.error("Unsupported action: GitLabCIService.removeAllDefaultProjectPermissions()"); + log.debug("Unsupported action: GitLabCIService.removeAllDefaultProjectPermissions()"); } @Override @@ -327,12 +400,12 @@ public ConnectorHealth health() { @Override public void createProjectForExercise(ProgrammingExercise programmingExercise) throws ContinuousIntegrationException { - log.error("Unsupported action: GitLabCIService.createProjectForExercise()"); + log.debug("Unsupported action: GitLabCIService.createProjectForExercise()"); } @Override public Optional getWebHookUrl(String projectKey, String buildPlanId) { - log.error("Unsupported action: GitLabCIService.getWebHookUrl()"); + log.debug("Unsupported action: GitLabCIService.getWebHookUrl()"); return Optional.empty(); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIUserManagementService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIUserManagementService.java index 19f37bed1334..10bec4e5981f 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIUserManagementService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIUserManagementService.java @@ -63,6 +63,6 @@ public void updateCoursePermissions(Course updatedCourse, String oldInstructorGr } private void logUnsupportedAction() { - log.error("Please refer to the repository for user management."); + log.debug("Please refer to the repository for user management."); } } diff --git a/src/main/resources/templates/gitlabci/empty/regularRuns/.gitlab-ci.yml b/src/main/resources/templates/gitlabci/empty/regularRuns/.gitlab-ci.yml index 3a16b66b7143..1c5857d1767a 100644 --- a/src/main/resources/templates/gitlabci/empty/regularRuns/.gitlab-ci.yml +++ b/src/main/resources/templates/gitlabci/empty/regularRuns/.gitlab-ci.yml @@ -8,13 +8,14 @@ test-job: only: variables: - $CI_COMMIT_BRANCH == $ARTEMIS_SUBMISSION_GIT_BRANCH - allow_failure: true + refs: + - triggers variables: GIT_STRATEGY: none MAVEN_OPTS: -Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=[yyyy-MM-dd'T'HH:mm:ssX] -Dorg.slf4j.simpleLogger.logFile=${ARTEMIS_BUILD_LOGS_FILE} script: - git clone --branch ${ARTEMIS_TEST_GIT_BRANCH} ${CI_SERVER_PROTOCOL}://${ARTEMIS_TEST_GIT_USER}:${ARTEMIS_TEST_GIT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/${CI_PROJECT_NAMESPACE}/${ARTEMIS_TEST_GIT_REPOSITORY_SLUG} . - - git clone --branch ${ARTEMIS_SUBMISSION_GIT_BRANCH} ${CI_SERVER_PROTOCOL}://${ARTEMIS_TEST_GIT_USER}:${ARTEMIS_TEST_GIT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME} assignment + - git clone --branch ${ARTEMIS_SUBMISSION_GIT_BRANCH} ${CI_SERVER_PROTOCOL}://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME} assignment - export ARTEMIS_NOTIFICATION_SECRET=[hidden] # Workaround for overwriting the secret - export ARTEMIS_TEST_GIT_TOKEN=[hidden] # TODO: Install dependencies not provided by the Docker image @@ -41,6 +42,8 @@ upload-job: only: variables: - $CI_COMMIT_BRANCH == $ARTEMIS_SUBMISSION_GIT_BRANCH + refs: + - triggers variables: GIT_STRATEGY: none script: diff --git a/src/main/resources/templates/gitlabci/java/maven/regularRuns/.gitlab-ci.yml b/src/main/resources/templates/gitlabci/java/maven/regularRuns/.gitlab-ci.yml index 7e5b429dd5f8..e71e7961d5bd 100644 --- a/src/main/resources/templates/gitlabci/java/maven/regularRuns/.gitlab-ci.yml +++ b/src/main/resources/templates/gitlabci/java/maven/regularRuns/.gitlab-ci.yml @@ -8,13 +8,14 @@ test-job: only: variables: - $CI_COMMIT_BRANCH == $ARTEMIS_SUBMISSION_GIT_BRANCH - allow_failure: true + refs: + - triggers variables: GIT_STRATEGY: none MAVEN_OPTS: -Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=[yyyy-MM-dd'T'HH:mm:ssX] -Dorg.slf4j.simpleLogger.logFile=${ARTEMIS_BUILD_LOGS_FILE} script: - git clone --branch ${ARTEMIS_TEST_GIT_BRANCH} ${CI_SERVER_PROTOCOL}://${ARTEMIS_TEST_GIT_USER}:${ARTEMIS_TEST_GIT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/${CI_PROJECT_NAMESPACE}/${ARTEMIS_TEST_GIT_REPOSITORY_SLUG} . - - git clone --branch ${ARTEMIS_SUBMISSION_GIT_BRANCH} ${CI_SERVER_PROTOCOL}://${ARTEMIS_TEST_GIT_USER}:${ARTEMIS_TEST_GIT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME} assignment + - git clone --branch ${ARTEMIS_SUBMISSION_GIT_BRANCH} ${CI_SERVER_PROTOCOL}://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME} assignment - export ARTEMIS_NOTIFICATION_SECRET=[hidden] # Workaround for overwriting the secret - export ARTEMIS_TEST_GIT_TOKEN=[hidden] - mvn test -B && echo "ARTEMIS_BUILD_STATUS=success" > .env || echo "ARTEMIS_BUILD_STATUS=failed" > .env @@ -38,6 +39,8 @@ upload-job: only: variables: - $CI_COMMIT_BRANCH == $ARTEMIS_SUBMISSION_GIT_BRANCH + refs: + - triggers variables: GIT_STRATEGY: none script: diff --git a/src/main/resources/templates/gitlabci/rust/regularRuns/.gitlab-ci.yml b/src/main/resources/templates/gitlabci/rust/regularRuns/.gitlab-ci.yml index 39e416158a9c..d5f4a007ca2d 100644 --- a/src/main/resources/templates/gitlabci/rust/regularRuns/.gitlab-ci.yml +++ b/src/main/resources/templates/gitlabci/rust/regularRuns/.gitlab-ci.yml @@ -9,12 +9,13 @@ test-job: only: variables: - $CI_COMMIT_BRANCH == $ARTEMIS_SUBMISSION_GIT_BRANCH - allow_failure: true + refs: + - triggers variables: GIT_STRATEGY: none script: - git clone --branch ${ARTEMIS_TEST_GIT_BRANCH} ${CI_SERVER_PROTOCOL}://${ARTEMIS_TEST_GIT_USER}:${ARTEMIS_TEST_GIT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/${CI_PROJECT_NAMESPACE}/${ARTEMIS_TEST_GIT_REPOSITORY_SLUG} . - - git clone --branch ${ARTEMIS_SUBMISSION_GIT_BRANCH} ${CI_SERVER_PROTOCOL}://${ARTEMIS_TEST_GIT_USER}:${ARTEMIS_TEST_GIT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME} assignment + - git clone --branch ${ARTEMIS_SUBMISSION_GIT_BRANCH} ${CI_SERVER_PROTOCOL}://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME} assignment - export ARTEMIS_NOTIFICATION_SECRET=[hidden] # Workaround for overwriting the secret - export ARTEMIS_TEST_GIT_TOKEN=[hidden] - cargo nextest run --profile ci | tee -a "${ARTEMIS_BUILD_LOGS_FILE}" && echo "ARTEMIS_BUILD_STATUS=success" > .env || echo "ARTEMIS_BUILD_STATUS=failed" > .env @@ -39,6 +40,8 @@ upload-job: only: variables: - $CI_COMMIT_BRANCH == $ARTEMIS_SUBMISSION_GIT_BRANCH + refs: + - triggers variables: GIT_STRATEGY: none script: diff --git a/src/test/java/de/tum/cit/aet/artemis/core/connector/GitlabRequestMockProvider.java b/src/test/java/de/tum/cit/aet/artemis/core/connector/GitlabRequestMockProvider.java index 8c23c2389c41..6ac2c36c00db 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/connector/GitlabRequestMockProvider.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/connector/GitlabRequestMockProvider.java @@ -4,6 +4,7 @@ import static org.gitlab4j.api.models.AccessLevel.GUEST; import static org.gitlab4j.api.models.AccessLevel.MAINTAINER; import static org.gitlab4j.api.models.AccessLevel.REPORTER; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyLong; @@ -60,6 +61,7 @@ import org.gitlab4j.api.models.PipelineFilter; import org.gitlab4j.api.models.PipelineStatus; import org.gitlab4j.api.models.Project; +import org.gitlab4j.api.models.ProjectAccessToken; import org.gitlab4j.api.models.ProjectHook; import org.gitlab4j.api.models.ProtectedBranch; import org.gitlab4j.api.models.PushData; @@ -919,6 +921,15 @@ public void mockUpdateProject(boolean shouldFail) throws GitLabApiException { } } + public void mockCreateProjectAccessToken(boolean shouldFail) throws GitLabApiException { + if (shouldFail) { + doThrow(new GitLabApiException("Internal Error", 500)).when(projectApi).createProjectAccessToken(anyString(), anyString(), anyList(), any(), anyLong()); + } + else { + doReturn(new ProjectAccessToken()).when(projectApi).createProjectAccessToken(anyString(), anyString(), anyList(), any(), anyLong()); + } + } + public void mockGetBuildStatus(PipelineStatus pipelineStatus) throws GitLabApiException { Pipeline pipeline = new Pipeline(); pipeline.setId(1L); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/service/GitlabCIServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/service/GitlabCIServiceTest.java index fda58121a3ac..3321f9add124 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/service/GitlabCIServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/service/GitlabCIServiceTest.java @@ -171,9 +171,18 @@ void testTriggerBuildFails() throws GitLabApiException { void testConfigureBuildPlanSuccess() throws Exception { final ProgrammingExercise exercise = programmingExerciseRepository.findWithBuildConfigById(programmingExerciseId).orElseThrow(); final ProgrammingExerciseStudentParticipation participation = participationUtilService.addStudentParticipationForProgrammingExercise(exercise, TEST_PREFIX + "student1"); + final String repositoryPath = uriService.getRepositoryPathFromRepositoryUri(participation.getVcsRepositoryUri()); mockConfigureBuildPlan(participation, defaultBranch); + continuousIntegrationService.configureBuildPlan(participation, defaultBranch); + verifyMocks(); + verify(gitlab, atLeastOnce()).getProjectApi(); + verify(gitlab.getProjectApi(), atLeastOnce()).getProject(eq(repositoryPath)); + verify(gitlab.getProjectApi(), atLeastOnce()).updateProject(any(Project.class)); + verify(gitlab.getProjectApi(), atLeastOnce()).getOptionalVariable(eq(repositoryPath), anyString()); + verify(gitlab.getProjectApi(), atLeastOnce()).createVariable(eq(repositoryPath), anyString(), anyString(), any(), anyBoolean(), anyBoolean()); + verify(gitlab.getGroupApi(), never()).createVariable(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean()); } @Test @@ -198,9 +207,16 @@ void testCreateBuildPlanForExercise() throws GitLabApiException { continuousIntegrationService.createBuildPlanForExercise(exercise, "TEST-EXERCISE", participation.getVcsRepositoryUri(), null, null); verify(gitlab, atLeastOnce()).getProjectApi(); + verify(gitlab, atLeastOnce()).getGroupApi(); + verify(gitlab.getProjectApi(), atLeastOnce()).getProject(eq(repositoryPath)); verify(gitlab.getProjectApi(), atLeastOnce()).updateProject(any(Project.class)); + verify(gitlab.getProjectApi(), atLeastOnce()).getOptionalVariable(any(), anyString()); verify(gitlab.getProjectApi(), atLeastOnce()).createVariable(anyString(), anyString(), anyString(), any(), anyBoolean(), anyBoolean()); + + verify(gitlab.getGroupApi(), atLeastOnce()).getOptionalVariable(any(), anyString()); + verify(gitlab.getGroupApi(), atLeastOnce()).createVariable(anyString(), anyString(), anyString(), anyBoolean(), anyBoolean()); + var buildPlanOptional = buildPlanRepository.findByProgrammingExercises_IdWithProgrammingExercises(exercise.getId()); assertThat(buildPlanOptional).isPresent(); assertThat(buildPlanOptional.get().getBuildPlan()).isNotBlank(); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/test_repository/ProgrammingExerciseTestRepository.java b/src/test/java/de/tum/cit/aet/artemis/programming/test_repository/ProgrammingExerciseTestRepository.java index dba45e8f6298..19c04aac1ffa 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/test_repository/ProgrammingExerciseTestRepository.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/test_repository/ProgrammingExerciseTestRepository.java @@ -7,8 +7,6 @@ import java.util.Optional; import java.util.Set; -import jakarta.validation.constraints.NotNull; - import org.hibernate.Hibernate; import org.springframework.context.annotation.Primary; import org.springframework.data.jpa.repository.EntityGraph; @@ -16,7 +14,6 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; @@ -90,11 +87,6 @@ default List findAllWithBuildAndTestAfterDueDateInFuture() List findAllByCourse_TeachingAssistantGroupNameIn(Set groupNames); - @NotNull - default ProgrammingExercise findByIdWithBuildConfigElseThrow(long programmingExerciseId) throws EntityNotFoundException { - return getValueElseThrow(findWithBuildConfigById(programmingExerciseId), programmingExerciseId); - } - @EntityGraph(type = LOAD, attributePaths = { "templateParticipation", "solutionParticipation", "studentParticipations.team.students", "buildConfig" }) Optional findWithAllParticipationsAndBuildConfigById(long exerciseId); @@ -107,9 +99,6 @@ default ProgrammingExercise findByIdWithBuildConfigElseThrow(long programmingExe """) Optional findWithEagerTemplateAndSolutionParticipationsById(@Param("exerciseId") long exerciseId); - @EntityGraph(type = LOAD, attributePaths = { "buildConfig" }) - Optional findWithBuildConfigById(long exerciseId); - /** * Fetch the programming exercise with the build config, or throw an EntityNotFoundException if it cannot be found. * diff --git a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationGitlabCIGitlabSamlTest.java b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationGitlabCIGitlabSamlTest.java index 1f78277f6942..9f33287aabbe 100644 --- a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationGitlabCIGitlabSamlTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationGitlabCIGitlabSamlTest.java @@ -326,6 +326,7 @@ public void mockConfigureBuildPlan(ProgrammingExerciseParticipation participatio public void mockAddBuildPlanToGitLabRepositoryConfiguration(boolean shouldFail) throws GitLabApiException { gitlabRequestMockProvider.mockGetProject(shouldFail); gitlabRequestMockProvider.mockUpdateProject(shouldFail); + gitlabRequestMockProvider.mockCreateProjectAccessToken(shouldFail); } @Override