diff --git a/.github/workflows/broken_links_checker.yml b/.github/workflows/broken_links_checker.yml index d7a38b46..39612b76 100644 --- a/.github/workflows/broken_links_checker.yml +++ b/.github/workflows/broken_links_checker.yml @@ -13,6 +13,8 @@ on: jobs: linkChecker: runs-on: ubuntu-latest + permissions: + contents: read defaults: run: shell: "bash" diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 5e98525e..2d4aee00 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -17,7 +17,6 @@ jobs: } permissions: { contents: read, - checks: write, issues: read } concurrency: { diff --git a/.github/workflows/test_linux_build_on_windows.yml b/.github/workflows/test_linux_build_on_windows.yml index 53d206ff..78115ff1 100644 --- a/.github/workflows/test_linux_build_on_windows.yml +++ b/.github/workflows/test_linux_build_on_windows.yml @@ -9,6 +9,8 @@ on: jobs: build-on-linux: runs-on: ubuntu-latest + permissions: + contents: read defaults: run: shell: "bash" @@ -24,9 +26,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: "temurin" - java-version: | - 11 - 17 + java-version: 17 cache: "maven" - name: Set up Go uses: actions/setup-go@v5 @@ -64,8 +64,11 @@ jobs: name: project-keeper-jar path: artifact retention-days: 1 + run-on-windows: runs-on: windows-latest + permissions: + contents: read needs: build-on-linux steps: - name: Checkout the repository diff --git a/.github/workflows/test_on_windows.yml b/.github/workflows/test_on_windows.yml index 0c054630..9292d3a4 100644 --- a/.github/workflows/test_on_windows.yml +++ b/.github/workflows/test_on_windows.yml @@ -9,6 +9,8 @@ on: jobs: test: runs-on: windows-latest + permissions: + contents: read defaults: run: shell: "bash" @@ -24,6 +26,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: "temurin" + # Java 11 required for test project "my-test-project" java-version: | 11 17 diff --git a/doc/changes/changes_4.3.1.md b/doc/changes/changes_4.3.1.md index 026984d8..076ab297 100644 --- a/doc/changes/changes_4.3.1.md +++ b/doc/changes/changes_4.3.1.md @@ -1,6 +1,6 @@ -# Project Keeper 4.3.1, released 2024-05-?? +# Project Keeper 4.3.1, released 2024-05-13 -Code name: Fix CVE-2024-31573 in `org.xmlunit:xmlunit-core:jar:2.9.1:test` +Code name: Environment for GitHub workflow `ci-build.yml` ## Summary @@ -10,6 +10,10 @@ This release fixes vulnerability CVE-2024-31573 in `org.xmlunit:xmlunit-core:jar * #570: Fixed CVE-2024-31573 in `org.xmlunit:xmlunit-core:jar:2.9.1:test` +## Features + +* #566: Allowed specifying an environment for GitHub workflow `ci-build.yml` + ## Bugfixes * #571: Fixed failing version increment during dependency update @@ -17,8 +21,18 @@ This release fixes vulnerability CVE-2024-31573 in `org.xmlunit:xmlunit-core:jar ## Dependency Updates +### Project Keeper Root Project + +#### Plugin Dependency Updates + +* Updated `com.exasol:error-code-crawler-maven-plugin:2.0.1` to `2.0.3` + ### Project Keeper Shared Model Classes +#### Test Dependency Updates + +* Updated `org.mockito:mockito-core:5.11.0` to `5.12.0` + #### Plugin Dependency Updates * Updated `com.exasol:error-code-crawler-maven-plugin:2.0.2` to `2.0.3` @@ -40,6 +54,7 @@ This release fixes vulnerability CVE-2024-31573 in `org.xmlunit:xmlunit-core:jar #### Test Dependency Updates * Updated `com.exasol:project-keeper-shared-test-setup:4.3.0` to `4.3.1` +* Updated `org.mockito:mockito-junit-jupiter:5.11.0` to `5.12.0` * Updated `org.xmlunit:xmlunit-matchers:2.9.1` to `2.10.0` #### Plugin Dependency Updates @@ -77,6 +92,7 @@ This release fixes vulnerability CVE-2024-31573 in `org.xmlunit:xmlunit-core:jar #### Test Dependency Updates * Updated `org.jacoco:org.jacoco.agent:0.8.11` to `0.8.12` +* Updated `org.mockito:mockito-core:5.11.0` to `5.12.0` * Updated `org.xmlunit:xmlunit-matchers:2.9.1` to `2.10.0` #### Plugin Dependency Updates @@ -95,6 +111,8 @@ This release fixes vulnerability CVE-2024-31573 in `org.xmlunit:xmlunit-core:jar #### Test Dependency Updates * Updated `org.jacoco:org.jacoco.agent:0.8.11` to `0.8.12` +* Updated `org.mockito:mockito-core:5.11.0` to `5.12.0` +* Updated `org.mockito:mockito-junit-jupiter:5.11.0` to `5.12.0` * Updated `org.xmlunit:xmlunit-matchers:2.9.1` to `2.10.0` #### Plugin Dependency Updates diff --git a/doc/requirements/design.md b/doc/requirements/design.md index c5c61828..94285813 100644 --- a/doc/requirements/design.md +++ b/doc/requirements/design.md @@ -424,6 +424,19 @@ Covers: Needs: impl, utest, itest +#### Customize Environment for GitHub Workflow `ci-build.yml` +`dsn~customize-build-process.ci-build.environment~1` + +PK allows configuring a custom GitHub environment for `ci-build.yml`. + +Rationale: +* Some projects use GitHub Environments to manually approve running expensive builds. + +Covers: +* [`req~customize-build-process~0`](system_requirements.md#customize-build-process) + +Needs: impl, utest + #### Customize GitHub Workflow `release.yml` `dsn~customize-build-process.release~0` diff --git a/doc/user_guide/user_guide.md b/doc/user_guide/user_guide.md index e88aaba4..7a8926fe 100644 --- a/doc/user_guide/user_guide.md +++ b/doc/user_guide/user_guide.md @@ -254,6 +254,17 @@ build: Sonar will only run for the first version in the list. +#### Customize Workflow Environment + +If your project requires running workflow `ci-build.yml` in a certain GitHub environment, you can specify it like this: + +```yml +build: + workflows: + - name: "ci-build.yml" + environment: aws +``` + #### Customize Workflow Steps If your project requires additional or modified steps in the generated GitHub workflow `ci-build.yml` you can replace existing steps or insert additional steps: diff --git a/parent-pom/pom.xml b/parent-pom/pom.xml index 5ca2a132..6063a3e4 100644 --- a/parent-pom/pom.xml +++ b/parent-pom/pom.xml @@ -35,7 +35,7 @@ 3.6.3 5.10.2 2.10.0 - 5.11.0 + 5.12.0 UTF-8 UTF-8 true diff --git a/pom.xml b/pom.xml index 9ecddcae..72463692 100644 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ com.exasol error-code-crawler-maven-plugin - 2.0.1 + 2.0.3 verify diff --git a/project-keeper/error_code_config.yml b/project-keeper/error_code_config.yml index 5238762f..c634c6c1 100644 --- a/project-keeper/error_code_config.yml +++ b/project-keeper/error_code_config.yml @@ -2,4 +2,4 @@ error-tags: PK-CORE: packages: - com.exasol.projectkeeper - highest-index: 206 + highest-index: 207 diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/config/ProjectKeeperConfigReader.java b/project-keeper/src/main/java/com/exasol/projectkeeper/config/ProjectKeeperConfigReader.java index 548f43fb..4171e945 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/config/ProjectKeeperConfigReader.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/config/ProjectKeeperConfigReader.java @@ -150,20 +150,16 @@ private CustomWorkflow convertWorkflow(final Workflow workflow) { supportedWorkflowNames) .toString()); } - if (workflow.stepCustomizations == null || workflow.stepCustomizations.isEmpty()) { - throw new IllegalArgumentException(ExaError.messageBuilder("E-PK-CORE-203") - .message("Missing customized steps for workflow {{workflow name}} in file {{config file name}}.", - workflow.name, CONFIG_FILE_NAME) - .mitigation("Add at least one step or remove the workflow.").toString()); - } return CustomWorkflow.builder() // .workflowName(workflow.name) // + .environment(workflow.environment) // .steps(convertSteps(workflow.stepCustomizations)) // .build(); } private List convertSteps(final List stepCustomizations) { - return stepCustomizations.stream().map(this::convertStep).toList(); + return Optional.ofNullable(stepCustomizations).orElseGet(Collections::emptyList) // + .stream().map(this::convertStep).toList(); } private StepCustomization convertStep(final RawStepCustomization step) { diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/config/ProjectKeeperRawConfig.java b/project-keeper/src/main/java/com/exasol/projectkeeper/config/ProjectKeeperRawConfig.java index e4a69047..831a8f10 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/config/ProjectKeeperRawConfig.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/config/ProjectKeeperRawConfig.java @@ -395,6 +395,8 @@ public void setExasolDbVersions(final List exasolDbVersions) { public static class Workflow { /** Workflow name, e.g. {@code ci-build.yml} or {@code release.yml}. */ public String name; + /** GitHub environment, e.g. {@code aws}. */ + public String environment; /** List of customizations for the workflow. */ public List stepCustomizations; } diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/CiBuildWorkflowGenerator.java b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/CiBuildWorkflowGenerator.java index 14fddd98..dcd92e89 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/CiBuildWorkflowGenerator.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/CiBuildWorkflowGenerator.java @@ -35,9 +35,21 @@ FileTemplate createCiBuildWorkflow() { buildOptions.getExasolDbVersions().stream().map(this::quote).collect(joining(", "))); template.replacing("defaultExasolDbVersion", quote(buildOptions.getExasolDbVersions().get(0))); } - // [impl->dsn~customize-build-process.ci-build~0] - return new ContentCustomizingTemplate(template, - new GitHubWorkflowStepCustomizer(findCustomizations(CI_BUILD_WORKFLOW_NAME), buildType.buildJobId)); + final String buildJobId = buildType.buildJobId; + return createTemplate(template, CI_BUILD_WORKFLOW_NAME, buildJobId); + } + + private FileTemplate createTemplate(final FileTemplateFromResource template, final String workflowName, + final String buildJobId) { + final Optional workflow = buildOptions.getWorkflow(workflowName); + final List customizations = workflow.map(CustomWorkflow::getSteps) + .orElseGet(Collections::emptyList); + final String environmentName = workflow.map(CustomWorkflow::getEnvironment).orElse(null); + return new ContentCustomizingTemplate(template, new GitHubWorkflowCustomizer( + // [impl->dsn~customize-build-process.ci-build~0] + new GitHubWorkflowStepCustomizer(customizations, buildJobId), + // [impl->dsn~customize-build-process.ci-build.environment~1] + new GitHubWorkflowEnvironmentCustomizer(buildJobId, environmentName))); } private List findCustomizations(final String workflowName) { @@ -94,8 +106,9 @@ private FileTemplate createCustomizedWorkflow(final String workflowName, final S final FileTemplateFromResource template = new FileTemplateFromResource(WORKFLOW_PATH + workflowName, REQUIRE_EXACT); templateCustomizer.accept(template); + final List customizations = findCustomizations(workflowName); return new ContentCustomizingTemplate(template, - new GitHubWorkflowStepCustomizer(findCustomizations(workflowName), jobName)); + new GitHubWorkflowCustomizer(new GitHubWorkflowStepCustomizer(customizations, jobName))); } enum CiTemplateType { diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflow.java b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflow.java index ddcc2e47..d4c25f31 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflow.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflow.java @@ -98,6 +98,14 @@ Map getEnv() { return asMap(rawJob.get("env")); } + public String getEnvironment() { + return (String) rawJob.get("environment"); + } + + public void setEnvironment(final String environmentName) { + rawJob.put("environment", environmentName); + } + private Predicate hasId(final String id) { return step -> step.getId().equals(id); } diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowCustomizer.java b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowCustomizer.java new file mode 100644 index 00000000..40de021f --- /dev/null +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowCustomizer.java @@ -0,0 +1,47 @@ +package com.exasol.projectkeeper.validators.files; + +import static java.util.Arrays.asList; + +import java.util.List; + +/** + * This class customizes a GitHub workflow by delegating to a list of {@link WorkflowCustomizer}s. + */ +class GitHubWorkflowCustomizer implements ContentCustomizingTemplate.ContentCustomizer { + private static final String GENERATED_COMMENT = "# This file was generated by Project Keeper.\n"; + private final GitHubWorkflowIO yaml; + private final List customizer; + + GitHubWorkflowCustomizer(final WorkflowCustomizer... customizer) { + this(GitHubWorkflowIO.create(), asList(customizer)); + } + + GitHubWorkflowCustomizer(final GitHubWorkflowIO yaml, final List customizer) { + this.yaml = yaml; + this.customizer = customizer; + } + + @Override + public String customizeContent(final String content) { + final GitHubWorkflow workflow = yaml.loadWorkflow(content); + customizeWorkflow(workflow); + return GENERATED_COMMENT + yaml.dumpWorkflow(workflow); + } + + private void customizeWorkflow(final GitHubWorkflow workflow) { + this.customizer.forEach(c -> c.applyCustomization(workflow)); + } + + /** + * Interface for customizing a GitHub workflow. Implementations of this interface can modify the given workflow as + * required. + */ + interface WorkflowCustomizer { + /** + * Apply the customization to the given workflow. + * + * @param workflow workflow to customize + */ + void applyCustomization(GitHubWorkflow workflow); + } +} diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowEnvironmentCustomizer.java b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowEnvironmentCustomizer.java new file mode 100644 index 00000000..1bd64dcc --- /dev/null +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowEnvironmentCustomizer.java @@ -0,0 +1,29 @@ +package com.exasol.projectkeeper.validators.files; + +import com.exasol.errorreporting.ExaError; +import com.exasol.projectkeeper.validators.files.GitHubWorkflow.Job; + +class GitHubWorkflowEnvironmentCustomizer implements GitHubWorkflowCustomizer.WorkflowCustomizer { + + private final String environmentName; + private final String jobId; + + GitHubWorkflowEnvironmentCustomizer(final String jobId, final String environmentName) { + this.jobId = jobId; + this.environmentName = environmentName; + } + + // [impl->dsn~customize-build-process.ci-build.environment~1] + @Override + public void applyCustomization(final GitHubWorkflow workflow) { + if (environmentName == null) { + return; + } + final Job job = workflow.getJob(jobId); + if (job == null) { + throw new IllegalArgumentException(ExaError.messageBuilder("E-PK-CORE-207") + .message("GitHub Workflow does not have a job with ID {{job id}}", jobId).toString()); + } + job.setEnvironment(this.environmentName); + } +} diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowStepCustomizer.java b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowStepCustomizer.java index 72136c9d..c9a27417 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowStepCustomizer.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowStepCustomizer.java @@ -5,31 +5,17 @@ import com.exasol.projectkeeper.shared.config.workflow.StepCustomization; import com.exasol.projectkeeper.validators.files.GitHubWorkflow.Job; -class GitHubWorkflowStepCustomizer implements ContentCustomizingTemplate.ContentCustomizer { - private static final String GENERATED_COMMENT = "# This file was generated by Project Keeper.\n"; +class GitHubWorkflowStepCustomizer implements GitHubWorkflowCustomizer.WorkflowCustomizer { private final List customizations; - private final GitHubWorkflowIO yaml; private final String jobId; GitHubWorkflowStepCustomizer(final List customizations, final String jobId) { - this(GitHubWorkflowIO.create(), customizations, jobId); - } - - GitHubWorkflowStepCustomizer(final GitHubWorkflowIO yaml, final List customizations, - final String jobId) { - this.yaml = yaml; this.customizations = customizations; this.jobId = jobId; } @Override - public String customizeContent(final String content) { - final GitHubWorkflow workflow = yaml.loadWorkflow(content); - customizeWorkflow(workflow); - return GENERATED_COMMENT + yaml.dumpWorkflow(workflow); - } - - private void customizeWorkflow(final GitHubWorkflow workflow) { + public void applyCustomization(final GitHubWorkflow workflow) { final Job job = workflow.getJob(jobId); for (final StepCustomization customization : customizations) { applyCustomization(job, customization); diff --git a/project-keeper/src/main/resources/templates/.github/workflows/broken_links_checker.yml b/project-keeper/src/main/resources/templates/.github/workflows/broken_links_checker.yml index d7a38b46..39612b76 100644 --- a/project-keeper/src/main/resources/templates/.github/workflows/broken_links_checker.yml +++ b/project-keeper/src/main/resources/templates/.github/workflows/broken_links_checker.yml @@ -13,6 +13,8 @@ on: jobs: linkChecker: runs-on: ubuntu-latest + permissions: + contents: read defaults: run: shell: "bash" diff --git a/project-keeper/src/main/resources/templates/.github/workflows/ci-build-db-version-matrix.yml b/project-keeper/src/main/resources/templates/.github/workflows/ci-build-db-version-matrix.yml index 5e8cd865..801937f8 100644 --- a/project-keeper/src/main/resources/templates/.github/workflows/ci-build-db-version-matrix.yml +++ b/project-keeper/src/main/resources/templates/.github/workflows/ci-build-db-version-matrix.yml @@ -14,7 +14,6 @@ jobs: shell: "bash" permissions: contents: read - checks: write # Allow scacap/action-surefire-report concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.exasol_db_version }} cancel-in-progress: true diff --git a/project-keeper/src/main/resources/templates/.github/workflows/ci-build-native-build.yml b/project-keeper/src/main/resources/templates/.github/workflows/ci-build-native-build.yml index a3f12f55..07e17a51 100644 --- a/project-keeper/src/main/resources/templates/.github/workflows/ci-build-native-build.yml +++ b/project-keeper/src/main/resources/templates/.github/workflows/ci-build-native-build.yml @@ -10,6 +10,8 @@ jobs: build: name: Build native-image on ${{ matrix.os }} runs-on: ${{ matrix.os }} + permissions: + contents: read defaults: run: shell: "bash" diff --git a/project-keeper/src/main/resources/templates/.github/workflows/ci-build-next-java.yml b/project-keeper/src/main/resources/templates/.github/workflows/ci-build-next-java.yml index 07b10b07..d33f3c0a 100644 --- a/project-keeper/src/main/resources/templates/.github/workflows/ci-build-next-java.yml +++ b/project-keeper/src/main/resources/templates/.github/workflows/ci-build-next-java.yml @@ -15,7 +15,6 @@ jobs: shell: "bash" permissions: contents: read - checks: write # Allow scacap/action-surefire-report concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -35,9 +34,3 @@ jobs: mvn --batch-mode --update-snapshots clean package -DtrimStackTrace=false $skipNativeImage \ -Djava.version=17 \ -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn - - name: Publish Test Report for Java 17 - uses: scacap/action-surefire-report@v1 - if: ${{ always() && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' }} - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - fail_if_no_tests: false diff --git a/project-keeper/src/main/resources/templates/.github/workflows/ci-build.yml b/project-keeper/src/main/resources/templates/.github/workflows/ci-build.yml index 69c260db..b10243b5 100644 --- a/project-keeper/src/main/resources/templates/.github/workflows/ci-build.yml +++ b/project-keeper/src/main/resources/templates/.github/workflows/ci-build.yml @@ -13,7 +13,6 @@ jobs: shell: "bash" permissions: contents: read - checks: write # Allow scacap/action-surefire-report issues: read # Required for PK verify-release concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/project-keeper/src/test/java/com/exasol/projectkeeper/config/ProjectKeeperConfigReaderTest.java b/project-keeper/src/test/java/com/exasol/projectkeeper/config/ProjectKeeperConfigReaderTest.java index e106b010..e7c6db62 100644 --- a/project-keeper/src/test/java/com/exasol/projectkeeper/config/ProjectKeeperConfigReaderTest.java +++ b/project-keeper/src/test/java/com/exasol/projectkeeper/config/ProjectKeeperConfigReaderTest.java @@ -260,20 +260,7 @@ static Stream invalidConfig() { stepId: step-id-to-replace content: """, equalTo( - "E-PK-CORE-202: Missing content in step customization of file '.project-keeper.yml'. Add content to the step customization.")), - Arguments.of("workflow: missing step customization list", """ - build: - workflows: - - name: ci-build.yml - """, equalTo( - "E-PK-CORE-203: Missing customized steps for workflow 'ci-build.yml' in file '.project-keeper.yml'. Add at least one step or remove the workflow.")), - Arguments.of("workflow: empty step customization list", """ - build: - workflows: - - name: ci-build.yml - stepCustomizations: - """, equalTo( - "E-PK-CORE-203: Missing customized steps for workflow 'ci-build.yml' in file '.project-keeper.yml'. Add at least one step or remove the workflow."))); + "E-PK-CORE-202: Missing content in step customization of file '.project-keeper.yml'. Add content to the step customization."))); } @ParameterizedTest(name = "{0}") @@ -287,6 +274,16 @@ void testReadingFails(final String testName, final String content, final Matcher assertThat(testName, exception.getMessage(), expectedErrorMessage); } + @Test + void readWorkflowWithoutCustomization() throws IOException { + writeProjectKeeperConfig(""" + build: + workflows: + - name: ci-build.yml + """); + assertThat(readConfig().getCiBuildConfig().getWorkflows().get(0).getSteps(), empty()); + } + @Test void readWorkflowCustomization() throws IOException { writeProjectKeeperConfig(""" @@ -331,6 +328,27 @@ void readWorkflowStepAction(final String action, final StepCustomization.Type ex equalTo(expected)); } + @Test + void readWorkflowWithoutEnvironment() throws IOException { + writeProjectKeeperConfig(""" + build: + workflows: + - name: ci-build.yml + """); + assertThat(readConfig().getCiBuildConfig().getWorkflows().get(0).getEnvironment(), nullValue()); + } + + @Test + void readWorkflowWithEnvironment() throws IOException { + writeProjectKeeperConfig(""" + build: + workflows: + - name: ci-build.yml + environment: aws + """); + assertThat(readConfig().getCiBuildConfig().getWorkflows().get(0).getEnvironment(), equalTo("aws")); + } + @Test void notInProjectRoot() throws IOException { Files.delete(this.tempDir.resolve(".git")); diff --git a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/dependencies/DependenciesValidatorIT.java b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/dependencies/DependenciesValidatorIT.java index 42c685f8..46a1036c 100644 --- a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/dependencies/DependenciesValidatorIT.java +++ b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/dependencies/DependenciesValidatorIT.java @@ -101,7 +101,7 @@ void testBrokenLinkReplacing() throws IOException { private void createExamplePomFile() throws IOException { final TestMavenModel pomModel = new TestMavenModel(); - pomModel.addDependency("error-reporting-java", "com.exasol", "compile", "0.1.0"); + pomModel.addDependency("error-reporting-java", "com.exasol", "compile", "1.0.1"); pomModel.configureAssemblyPluginFinalName(); pomModel.writeAsPomToProject(this.projectDir); } @@ -113,7 +113,7 @@ private void createExamplePomFileWithNonDefaultRepo() throws IOException { exasolRepo.setUrl("https://repo1.maven.org/maven2"); exasolRepo.setId("maven.exasol.com"); pomModel.addRepository(exasolRepo); - pomModel.addDependency("exasol-jdbc", "com.exasol", "compile", "7.0.4"); + pomModel.addDependency("exasol-jdbc", "com.exasol", "compile", "24.1.0"); pomModel.writeAsPomToProject(this.projectDir); } } diff --git a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/CiBuildWorkflowGeneratorTest.java b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/CiBuildWorkflowGeneratorTest.java index f8764842..d5eb8c6b 100644 --- a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/CiBuildWorkflowGeneratorTest.java +++ b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/CiBuildWorkflowGeneratorTest.java @@ -133,6 +133,21 @@ void ciBuildMatrixBuildSingleVersion() { "${{ env.SONAR_TOKEN != null && matrix.exasol_db_version == env.DEFAULT_EXASOL_DB_VERSION }}"))); } + // [utest->dsn~customize-build-process.ci-build.environment~1] + @Test + void ciBuildCustomEnvironment() { + final Job job = ciBuildContent(BuildOptions.builder() + .workflows(List.of(CustomWorkflow.builder().workflowName("ci-build.yml").environment("aws").build()))) + .getJob("build"); + assertThat(job.getEnvironment(), equalTo("aws")); + } + + @Test + void ciBuildDefaultHasNoEnvironment() { + final Job job = ciBuildContent(BuildOptions.builder()).getJob("build"); + assertThat(job.getEnvironment(), nullValue()); + } + // [utest->dsn~release-workflow.deploy-maven-central~1] @Test void releaseBuildWithMavenRelease() { diff --git a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/FileTemplatesFactoryTest.java b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/FileTemplatesFactoryTest.java index 36f8305a..7dd05ac6 100644 --- a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/FileTemplatesFactoryTest.java +++ b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/FileTemplatesFactoryTest.java @@ -26,7 +26,6 @@ @ExtendWith(MockitoExtension.class) class FileTemplatesFactoryTest { private static final String OWN_VERSION = "version"; - private static final String NEWLINE = System.lineSeparator(); @Mock Logger loggerMock; diff --git a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowCustomizerTest.java b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowCustomizerTest.java new file mode 100644 index 00000000..06c5379d --- /dev/null +++ b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowCustomizerTest.java @@ -0,0 +1,104 @@ +package com.exasol.projectkeeper.validators.files; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.startsWith; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.inOrder; + +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.exasol.projectkeeper.shared.config.workflow.WorkflowStep; + +@ExtendWith(MockitoExtension.class) +class GitHubWorkflowCustomizerTest { + + @Mock + GitHubWorkflowCustomizer.WorkflowCustomizer customizerMock1; + @Mock + GitHubWorkflowCustomizer.WorkflowCustomizer customizerMock2; + + @Test + void startsWithGeneratedComment() { + final String yaml = getCustomizedContent(""" + jobs: + build: + steps: + """); + assertThat(yaml, startsWith("# This file was generated by Project Keeper.\n")); + } + + @Test + void noChangesWithEmptyCustomizerList() { + assertThat(getCustomizedContent(""" + jobs: + build: + steps: + """), equalTo(""" + # This file was generated by Project Keeper. + jobs: + build: { + steps: null + } + """)); + } + + @Test + void noChangesWithNoOpCustomizer() { + assertThat(getCustomizedContent(""" + jobs: + build: + steps: + """, (workflow) -> { + }), equalTo(""" + # This file was generated by Project Keeper. + jobs: + build: { + steps: null + } + """)); + } + + @Test + void customizerUpdatesWorkflow() { + assertThat(getCustomizedContent(""" + jobs: + build: + steps: + - id: step1 + """, (workflow) -> { + workflow.getJob("build").insertStepAfter("step1", WorkflowStep.createStep(Map.of("id", "step2"))); + }), equalTo(""" + # This file was generated by Project Keeper. + jobs: + build: + steps: + - { + id: step1 + } + - { + id: step2 + } + """)); + } + + @Test + void customizerCalledInOrder() { + new GitHubWorkflowCustomizer(customizerMock1, customizerMock2).customizeContent(""); + final InOrder inOrder = inOrder(customizerMock1, customizerMock2); + inOrder.verify(customizerMock1).applyCustomization(any()); + inOrder.verify(customizerMock2).applyCustomization(any()); + inOrder.verifyNoMoreInteractions(); + } + + private String getCustomizedContent(final String workflowTemplate, + final GitHubWorkflowCustomizer.WorkflowCustomizer... customizer) { + return new GitHubWorkflowCustomizer(customizer).customizeContent(workflowTemplate); + } +} diff --git a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowEnvironmentCustomizerTest.java b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowEnvironmentCustomizerTest.java new file mode 100644 index 00000000..13744964 --- /dev/null +++ b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowEnvironmentCustomizerTest.java @@ -0,0 +1,50 @@ +package com.exasol.projectkeeper.validators.files; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class GitHubWorkflowEnvironmentCustomizerTest { + + @Test + void updateEnvironment() { + final GitHubWorkflow workflow = customize(""" + jobs: + build: + steps: + """, new GitHubWorkflowEnvironmentCustomizer("build", "my-env")); + assertThat(workflow.getJob("build").getEnvironment(), equalTo("my-env")); + } + + @Test + void environmentIsNull() { + final GitHubWorkflow workflow = customize(""" + jobs: + build: + steps: + """, new GitHubWorkflowEnvironmentCustomizer("build", null)); + assertThat(workflow.getJob("build").getEnvironment(), nullValue()); + } + + @Test + void unknownJobId() { + final GitHubWorkflowEnvironmentCustomizer customizer = new GitHubWorkflowEnvironmentCustomizer("unknown-job", + "my-env"); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> customize(""" + jobs: + build: + steps: + """, customizer)); + assertThat(exception.getMessage(), + equalTo("E-PK-CORE-207: GitHub Workflow does not have a job with ID 'unknown-job'")); + } + + GitHubWorkflow customize(final String yaml, final GitHubWorkflowCustomizer.WorkflowCustomizer customizer) { + final GitHubWorkflow workflow = GitHubWorkflowIO.create().loadWorkflow(yaml); + customizer.applyCustomization(workflow); + return workflow; + } +} diff --git a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowStepCustomizerTest.java b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowStepCustomizerTest.java index 1e93e207..6e2d0ba8 100644 --- a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowStepCustomizerTest.java +++ b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowStepCustomizerTest.java @@ -214,6 +214,7 @@ private GitHubWorkflow validate(final String workflowTemplate, final StepCustomi } private String getCustomizedContent(final String workflowTemplate, final StepCustomization... customizations) { - return new GitHubWorkflowStepCustomizer(asList(customizations), "build").customizeContent(workflowTemplate); + return new GitHubWorkflowCustomizer(new GitHubWorkflowStepCustomizer(asList(customizations), "build")) + .customizeContent(workflowTemplate); } } diff --git a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowTest.java b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowTest.java index 212997a8..6d3995a9 100644 --- a/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowTest.java +++ b/project-keeper/src/test/java/com/exasol/projectkeeper/validators/files/GitHubWorkflowTest.java @@ -322,6 +322,17 @@ void insertStepMissing() { "E-PK-CORE-205: No step found for id 'missing' in {steps=[{id=step-to-replace, name=Old Step}]}")); } + @Test + void setEnvironment() { + final Job job = read(""" + jobs: + build: + steps: + """).getJob("build"); + job.setEnvironment("my-env"); + assertThat(job.getEnvironment(), equalTo("my-env")); + } + private GitHubWorkflow read(final String yaml) { return GitHubWorkflowIO.create().loadWorkflow(yaml); } diff --git a/shared-model-classes/src/main/java/com/exasol/projectkeeper/shared/config/workflow/CustomWorkflow.java b/shared-model-classes/src/main/java/com/exasol/projectkeeper/shared/config/workflow/CustomWorkflow.java index f3d7a9a3..6a393a67 100644 --- a/shared-model-classes/src/main/java/com/exasol/projectkeeper/shared/config/workflow/CustomWorkflow.java +++ b/shared-model-classes/src/main/java/com/exasol/projectkeeper/shared/config/workflow/CustomWorkflow.java @@ -10,10 +10,12 @@ public final class CustomWorkflow { private final String workflowName; + private final String environment; private final List steps; private CustomWorkflow(final Builder builder) { this.workflowName = Objects.requireNonNull(builder.workflowName, "workflowName"); + this.environment = builder.environment; this.steps = builder.steps == null ? emptyList() : builder.steps; } @@ -35,6 +37,16 @@ public List getSteps() { return steps; } + /** + * Get the GitHub environment for the workflow, e.g. {@code aws}. If this is {@code null}, the workflow will not use + * an environment. + * + * @return the GitHub environment + */ + public String getEnvironment() { + return environment; + } + /** * Create a new builder for the {@link CustomWorkflow}. * @@ -46,12 +58,13 @@ public static Builder builder() { @Override public String toString() { - return "WorkflowOptions [workflowName=" + workflowName + ", steps=" + steps + "]"; + return "WorkflowOptions [workflowName=" + workflowName + ", environment=" + environment + ", steps=" + steps + + "]"; } @Override public int hashCode() { - return Objects.hash(workflowName, steps); + return Objects.hash(workflowName, environment, steps); } @Override @@ -66,7 +79,8 @@ public boolean equals(final Object obj) { return false; } final CustomWorkflow other = (CustomWorkflow) obj; - return Objects.equals(workflowName, other.workflowName) && Objects.equals(steps, other.steps); + return Objects.equals(workflowName, other.workflowName) && Objects.equals(environment, other.environment) + && Objects.equals(steps, other.steps); } /** @@ -74,6 +88,7 @@ public boolean equals(final Object obj) { */ public static class Builder { private String workflowName; + private String environment; private List steps = null; private Builder() { @@ -115,6 +130,18 @@ public Builder steps(final List steps) { return this; } + /** + * Set GitHub environment for the workflow, e.g. {@code aws}. If this is {@code null}, the workflow will not use + * an environment. + * + * @param environment the GitHub environment + * @return the builder instance + */ + public Builder environment(final String environment) { + this.environment = environment; + return this; + } + /** * Build the BuildWorkflow instance. *