Thursday, April 30, 2020

How to Create a Maven Plugin




In a recent project I've had to create a maven plugin.
The maven plugin goal was to analyze a file, and set a maven property based on the file content.
In this post, we will review the step to create such a maven plugin, and to use it.


Creating the Maven Plugin


To create the maven plugin, we create a new maven project with 2 files:

  • pom.xml - configuring the maven plugin build
  • MyPlugin.java - doing the actual plugin work

The pom.xml is pretty simple, we use maven-plugin-plugin (a very creative name...) to build a new plugin.


<project>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.demo</groupId>
    <artifactId>analyze-file</artifactId>
    <packaging>maven-plugin</packaging>



    <dependencies>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>3.6.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.6.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-project</artifactId>
            <version>2.2.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-plugin-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
                </configuration>
                <executions>
                    <execution>
                        <id>mojo-descriptor</id>
                        <goals>
                            <goal>descriptor</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>


The MyPlugin.java does the actual work.
It analyzes a file, and sets a maven property based on the analysis.
Both the input file name, and the output property are configurable in the plugin run. We will later see this while presenting the usage of the plugin.
We will use the @Parameter annotation to specify a configurable parameter.


package com.dfc.maven.plugin.parsejar;


import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.io.File;

@Mojo(name = "parse")
public class MyPlugin extends AbstractMojo {

    @Component
    private MavenProject project;

    @Parameter
    private String inputFile;

    @Parameter
    private String toProperty;

    public void execute() throws MojoExecutionException {
        try {
            doWork();
        } catch (Throwable e) {
            e.printStackTrace();
            throw new MojoExecutionException("analyze " + inputFile + " failed", e);
        }
    }

    private void doWork() throws Exception {
        File file = new File(inputFile);
        getLog().info("parsing file: " + file.getAbsolutePath());

        // ... analyze the file, and set the property value
        String value = ...;

        project.getProperties().put(toProperty, value);
    }
}


Using the Maven Plugin


To use the plugin, we add it as part of another maven project's pom.xml, for example:


<build>
    <plugins>
        <plugin>
            <artifactId>analyze-file</artifactId>
            <groupId>com.demo</groupId>
            <executions>
                <execution>
                    <goals>
                        <goal>parse</goal>
                    </goals>
                    <configuration>
                        <inputFile>${basedir}/my-input-file.txt</inputFile>
                        <toProperty>analyze-output</toProperty>
                    </configuration>
                </execution>
            </executions>
        </plugin>


The configurable properties are listed as part of the configuration element of the plugin usage.


Summary


In this post we have create a configurable maven plugin, which can be reused among other maven project. By creation of our own maven plugin, we can extend the maven build to match our specialized requirements.

Friday, April 24, 2020

Use AWS S3 to Update Server Files


This post is about a nice trick that a colleague of mine had found.

We have a server running on a production data center, and so the access to this server is through several security layers. We run some POC test code on this server, and had to update the application binary several times. As this was a POC stage project, we had no CI/CD process to automate deployment of the binary, and we've had to upload the code manually. But uploading the code was extremely slow and complicated, due to the multiple security layers.

The solution we've finally used was to upload the application binary to a AWS S3 bucket, and use a python code to download it on the production server.

The first step is to create an AWS S3 bucket.
In his case I've create a bucket named binary-bucket.



Then, upload the binary to this bucket.



To retrieve the application binary on the production server, we' have use a short python script.
Notice that due to the security tiers, we could not directly access the AWS S3, so we've used a proxy instead.

import boto3
from botocore.config import Config

BUCKET_NAME = 'binary-bucket'
BINARY_FILE = 'app.bin'
PROXY = '10.1.1.1:443'

s3 = boto3.client('s3', config=Config(proxies={'https': PROXY}))
obj = s3.get_object(Bucket=BUCKET_NAME, Key=BINARY_FILE)
data = obj['Body'].read()
with open(BINARY_FILE, 'wb') as code:
    code.write(data)

The last thing to do, is to use an AWS access key for the AWS authentication.
This is done by creating of a configuration file inthe home folder: ~/.aws/config


[default]
aws_access_key_id=A12DEHAA72B4PAAATJRA
aws_secret_access_key=TelYU6M3Dfg/ssl3PWJAPSwXg/rJw9kD44Rdd7sq

and that's it. The binary is downloaded within seconds.


Update: Upload to AWS S3

After a week of work, I've found that another useful task is to retrieve files back from the production server. So I've added an upload script.

import boto3
from botocore.config import Config

BUCKET_NAME = 'binary-bucket'
UPLOAD_FILE = 'app.data'
PROXY = '10.1.1.1:443'

s3 = boto3.client('s3', config=Config(proxies={'https': PROXY}))
s3.upload_file(UPLOAD_FILE, BUCKET_NAME, UPLOAD_FILE)


and we're done.

Thursday, April 16, 2020

Capture HTTP Transactions into HAR file

This week I've created a utility to capture HTTP (clear text) transactions into a HAR file.
See more details in the repository: https://github.com/alonana/httshark

This is useful in case you want to view transactions that are sent to a none secured site.
Another common usage is after an SSL terminator, as illustrated below: