In praise of frontend-maven-plugin

Here is an example of using frontend-maven-plugin to integrate a node app into the maven lifecycle and build with Maven.

Some explanation:

  • There was no internet access, so we had to host the node and yarn executables in an Artifactory instance.
  • We bind the script run build to the generate-resources lifecycle phase.
  • The unit test script int is bound to the test phase.
  • Lastly, the integration test script e2e is bound to the integreation-test phase.
  • The node app can be started via mvn frontend:yarn@start which is bound to yarn start.
<plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <version>${frontend-maven-plugin.version}</version>
    <configuration>
        <nodeVersion>${node.version}</nodeVersion>
        <yarnVersion>${yarn.version}</yarnVersion>
        <workingDirectory>${project.basedir}</workingDirectory>
        <installDirectory>${project.build.directory}</installDirectory>
        <serverId>central</serverId>
        <nodeDownloadRoot>https://artifactory.company.au/artifactory/ext-releases-local/node/</nodeDownloadRoot>
        <yarnDownloadRoot>https://artifactory.company.au/artifactory/ext-releases-local/yarn/</yarnDownloadRoot>
    </configuration>
    <executions>
        <execution>
            <id>install-frontend-tools</id>
            <goals>
                <goal>install-node-and-yarn</goal>
            </goals>
        </execution>
        <execution>
            <id>yarn-config-strict-ssl</id>
            <goals>
                <goal>yarn</goal>
            </goals>
            <configuration>
                <arguments>config set strict-ssl false</arguments>
            </configuration>
        </execution>
        <execution>
            <id>yarn-install</id>
            <goals>
                <goal>yarn</goal>
            </goals>
            <!-- optional: the default phase is "generate-resources" -->
            <phase>generate-resources</phase>
            <configuration>
                <arguments>install</arguments>
                <npmRegistryURL>https://artifactory.company.au/artifactory/api/npm/npm-remote/</npmRegistryURL>
            </configuration>
        </execution>
        <execution>
            <id>build-frontend</id>
            <goals>
                <goal>yarn</goal>
            </goals>
            <!-- optional: the default phase is "generate-resources" -->
            <phase>generate-resources</phase>
            <configuration>
                <arguments>run build</arguments>
            </configuration>
        </execution>
        <execution>
            <id>test</id>
            <goals>
                <goal>yarn</goal>
            </goals>
            <phase>test</phase>
            <configuration>
                <arguments>int</arguments>
            </configuration>
        </execution>
        <execution>
            <id>integration-test</id>
            <goals>
                <goal>yarn</goal>
            </goals>
            <phase>integration-test</phase>
            <configuration>
                <arguments>e2e</arguments>
            </configuration>
        </execution>
        <!--call from command line with: mvn frontend:yarn@start-->
        <execution>
            <id>start</id>
            <goals>
                <goal>yarn</goal>
            </goals>
            <phase>none</phase>
            <configuration>
                <arguments>start</arguments>
            </configuration>
        </execution>
    </executions>
</plugin>

Parameter pool in Jenkins Pipeline

Given the need for scripted Jenkins pipeline jobs to choose a resource from a finite set, we can use the following in our Jenkinsfile to choose an available resource.

import org.jenkins.plugins.lockableresources.LockableResourcesManager as manager

def getResource(resources) {
    return resources.find { 
        resource -> manager.get().fromName(resource) == null ? resource : 
                manager.get().fromName(resource).with { r -> !r.isLocked() && !r.isQueued() } }
}

node() {
    stage('Deploy') {
        def database = null
        waitUntil {
            database = getResource(["DB1", "DB2", "DB3", "DB4"])
        }
        ...
        lock(database) {
            ...
        }
    }
}

The Parameter Pool plugin does appear to be compatible with Jenkins pipelines, so this is solution for pipelines.

Generate JSON in Spring Boot tests

JacksonTester is a utility class included in the Spring Boot Test module to generate and parse JSON.

For example, to easily compare the response from a REST API with an object expectedStatus of type JobStatusDto.

@WebMvcTest(TransactionController.class)
@AutoConfigureJsonTesters
public class TransactionControllerTest {

    @MockBean
    private TransactionService transactionService;

    // This object will be initialised thanks to @AutoConfigureJsonTesters
    @Autowired
    private JacksonTester<JobStatusDto> jsonJobStatus;

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testGetJobStatus() throws Exception {
        // given
        long pJobId = 1L;
        JobStatusDto expectedStatus = new JobStatusDto(pJobId, "status", "message", "detailedMessage");
        given(transactionService.getJobStatus(pJobId)).willReturn(expectedStatus);

        // when
        MockHttpServletResponse response = mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/job/1")).andReturn().getResponse();

        // then
        assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
        assertThat(response.getContentAsString())
                .isEqualTo(jsonJobStatus.write(expectedStatus).getJson());
    }
    
}

See also: