Skip to content

Commit 08bccc5

Browse files
Added Docker build (#126)
* Added Docker build * Bugfix Build and push Docker image * Bugfix actions * Bugfix github actions * Bugfix actions * Bugfix actions * Added push on Docker Hub * Bugfix actions * Bugfix actions * Test * Bugfix actions * Bugfix actions * Fixed build docker GitHub Container Registry * Added publish to PyPI * Improved cli code * Fixed tests and minor refactoring * Minor changes
1 parent 5340c6b commit 08bccc5

File tree

10 files changed

+436
-177
lines changed

10 files changed

+436
-177
lines changed

.github/workflows/main.yml

+94-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Python application
1+
name: Python application and Docker image CI
22

33
on:
44
push:
@@ -65,3 +65,96 @@ jobs:
6565
path: |
6666
dist/mail-parser-*.tar.gz
6767
dist/mail_parser-*.whl
68+
69+
- name: Publish to PyPI
70+
if: matrix.python-version == '3.10' && startsWith(github.ref, 'refs/tags/')
71+
uses: pypa/gh-action-pypi-publish@v1.5.1
72+
with:
73+
user: ${{ secrets.PYPI_USERNAME }}
74+
password: ${{ secrets.PYPI_PASSWORD }}
75+
76+
docker:
77+
runs-on: ubuntu-latest
78+
needs: build
79+
steps:
80+
- uses: actions/checkout@v4
81+
82+
- name: Set up Docker Buildx
83+
uses: docker/setup-buildx-action@v3
84+
85+
- name: Log in to GitHub Container Registry
86+
uses: docker/login-action@v3
87+
with:
88+
registry: ghcr.io
89+
username: ${{ github.actor }}
90+
password: ${{ secrets.GITHUB_TOKEN }}
91+
92+
- name: Log in to Docker Hub
93+
uses: docker/login-action@v3
94+
with:
95+
registry: docker.io
96+
username: ${{ secrets.DOCKER_USERNAME }}
97+
password: ${{ secrets.DOCKER_PASSWORD }}
98+
99+
- name: Extract branch or tag name
100+
id: extract_ref
101+
run: |
102+
if [ -n "${GITHUB_HEAD_REF}" ]; then
103+
REF_NAME=${GITHUB_HEAD_REF}
104+
else
105+
REF_NAME=$(git describe --tags --exact-match 2>/dev/null || git rev-parse --abbrev-ref HEAD)
106+
fi
107+
echo "REF_NAME=${REF_NAME,,}" >> $GITHUB_ENV
108+
109+
- name: Debug REF_NAME
110+
run: echo "REF_NAME=${{ env.REF_NAME }}"
111+
112+
- name: Build and push Docker image on GitHub Container Registry
113+
run: |
114+
cd docker
115+
IMAGE_NAME=ghcr.io/ghcr.io/spamscope/mail-parser/mailparser
116+
if [[ $GITHUB_REF == refs/tags/* ]]; then
117+
TAG=${GITHUB_REF#refs/tags/}
118+
docker build \
119+
--label "org.opencontainers.image.source=${{ github.repositoryUrl }}" \
120+
--label "org.opencontainers.image.description=Easy way to pass from raw mail to Python object" \
121+
--label "org.opencontainers.image.licenses=Apache-2.0" \
122+
--build-arg BRANCH=$TAG \
123+
-t $IMAGE_NAME:$TAG \
124+
-t $IMAGE_NAME:latest .
125+
docker push $IMAGE_NAME:$TAG
126+
docker push $IMAGE_NAME:latest
127+
else
128+
docker build \
129+
--label "org.opencontainers.image.source=${{ github.repositoryUrl }}" \
130+
--label "org.opencontainers.image.description=Easy way to pass from raw mail to Python object" \
131+
--label "org.opencontainers.image.licenses=Apache-2.0" \
132+
--build-arg BRANCH=${{ env.REF_NAME }} \
133+
-t $IMAGE_NAME:develop .
134+
docker push $IMAGE_NAME:develop
135+
fi
136+
137+
- name: Build and push Docker image on Docker Hub
138+
run: |
139+
cd docker
140+
IMAGE_NAME=docker.io/${{ secrets.DOCKER_USERNAME }}/spamscope-mail-parser
141+
if [[ $GITHUB_REF == refs/tags/* ]]; then
142+
TAG=${GITHUB_REF#refs/tags/}
143+
docker build \
144+
--label "org.opencontainers.image.source=${{ github.repositoryUrl }}" \
145+
--label "org.opencontainers.image.description=Easy way to pass from raw mail to Python object" \
146+
--label "org.opencontainers.image.licenses=Apache-2.0" \
147+
--build-arg BRANCH=$TAG \
148+
-t $IMAGE_NAME:$TAG \
149+
-t $IMAGE_NAME:latest .
150+
docker push $IMAGE_NAME:$TAG
151+
docker push $IMAGE_NAME:latest
152+
else
153+
docker build \
154+
--label "org.opencontainers.image.source=${{ github.repositoryUrl }}" \
155+
--label "org.opencontainers.image.description=Easy way to pass from raw mail to Python object" \
156+
--label "org.opencontainers.image.licenses=Apache-2.0" \
157+
--build-arg BRANCH=${{ env.REF_NAME }} \
158+
-t $IMAGE_NAME:develop .
159+
docker push $IMAGE_NAME:develop
160+
fi

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ clean-tests: ## remove test and coverage artifacts
4545

4646
clean-all: clean-tests clean-build ## remove all tests and build files
4747

48-
test: clean-tests ## run tests quickly with the default Python
48+
unittest: clean-tests ## run tests quickly with the default Python
4949
pytest
5050

5151
pre-commit: ## run pre-commit on all files

docker/Dockerfile

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
FROM python
1+
FROM python:3.10-slim-bullseye
22
ENV MAIL_PARSER_PATH=/tmp/mailparser
33
ARG BRANCH=develop
4-
RUN apt-get -yqq update; \
5-
apt-get -yqq --no-install-recommends install libemail-outlook-message-perl; \
6-
apt-get clean; \
7-
rm -rf /var/lib/apt/lists/*; \
8-
git clone -b $BRANCH --single-branch https://github.com/SpamScope/mail-parser.git $MAIL_PARSER_PATH; \
9-
cd $MAIL_PARSER_PATH && python setup.py install
10-
ENTRYPOINT ["mailparser"]
4+
RUN apt-get -yqq update && \
5+
apt-get -yqq --no-install-recommends install libemail-outlook-message-perl git && \
6+
apt-get clean && \
7+
rm -rf /var/lib/apt/lists/* && \
8+
git clone -b ${BRANCH} --single-branch https://github.com/SpamScope/mail-parser.git ${MAIL_PARSER_PATH} && \
9+
cd ${MAIL_PARSER_PATH} && \
10+
python setup.py install
11+
ENTRYPOINT ["mail-parser"]
1112
CMD ["-h"]

docker/README.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
1-
[![Build Status](https://travis-ci.org/SpamScope/mail-parser.svg?branch=develop)](https://travis-ci.org/SpamScope/mail-parser)
2-
[![](https://images.microbadger.com/badges/image/fmantuano/spamscope-mail-parser.svg)](https://microbadger.com/images/fmantuano/spamscope-mail-parser "Get your own image badge on microbadger.com")
1+
[![PyPI - Version](https://img.shields.io/pypi/v/mail-parser)](https://pypi.org/project/mail-parser/)
2+
[![Coverage Status](https://coveralls.io/repos/github/SpamScope/mail-parser/badge.svg?branch=develop)](https://coveralls.io/github/SpamScope/mail-parser?branch=develop)
3+
[![PyPI - Downloads](https://img.shields.io/pypi/dm/mail-parser?color=blue)](https://pypistats.org/packages/mail-parser)
34

45
![SpamScope](https://raw.githubusercontent.com/SpamScope/spamscope/develop/docs/logo/spamscope.png)
56

67
# fmantuano/spamscope-mail-parser
7-
8-
This Dockerfile represents a Docker image that encapsulates mail-parser. The [official image](https://hub.docker.com/r/fmantuano/spamscope-mail-parser/) is on Docker Hub.
8+
This Dockerfile represents a Docker image that encapsulates `mail-parser`. The [official image](https://hub.docker.com/r/fmantuano/spamscope-mail-parser/) is on Docker Hub.
99

1010
To run this image after installing Docker, use a command like this:
1111

12-
```
12+
```shell
1313
sudo docker run -i -t --rm -v ~/mails:/mails fmantuano/spamscope-mail-parser
1414
```
1515

16-
This command runs mail-parser help as default, but you can use all others options.
16+
This command runs `mail-parser` help as default, but you can use all others options.
1717

1818
To share the "mails" directory between your host and the container, create a "mails" directory on your host.
1919

2020
There also is an example of `docker-compose`
2121

2222
From the `docker-compose.yml` directory, run:
2323

24-
```
24+
```shell
2525
$ sudo docker-compose up
2626
```
2727

28-
The provided ```docker-compose.yml``` file is configured to:
28+
The provided `docker-compose.yml` file is configured to:
2929

3030
- Mount your host's `~/mails/` folder from your source tree inside the container at `/mails/` (read-only).
3131
- A command line test example.
3232

33-
See the ```docker-compose.yml``` to view and tweak the launch parameters.
33+
See the `docker-compose.yml` to view and tweak the launch parameters.

report/.gitkeep

Whitespace-only changes.

src/mailparser/__main__.py

+118-29
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
"""
1919

2020
import argparse
21-
import os
22-
import runpy
21+
import logging
2322
import sys
2423

2524
import mailparser
@@ -31,14 +30,18 @@
3130
safe_print,
3231
write_attachments,
3332
)
33+
from mailparser.version import __version__
3434

3535

36-
current = os.path.realpath(os.path.dirname(__file__))
37-
38-
__version__ = runpy.run_path(os.path.join(current, "version.py"))["__version__"]
36+
log = logging.getLogger("mailparser")
3937

4038

4139
def get_args():
40+
"""
41+
Get arguments from command line.
42+
:return: argparse.ArgumentParser
43+
:rtype: argparse.ArgumentParser
44+
"""
4245
parser = argparse.ArgumentParser(
4346
description="Wrapper for email Python Standard Library",
4447
epilog="It takes as input a raw mail and generates a parsed object.",
@@ -189,22 +192,80 @@ def get_args():
189192

190193

191194
def main():
195+
"""
196+
Main function.
197+
"""
192198
args = get_args().parse_args()
193-
log = custom_log(level=args.log_level)
194-
199+
log = custom_log(level=args.log_level, name="mailparser")
200+
201+
try:
202+
parser = get_parser(args)
203+
process_output(args, parser)
204+
except Exception as e:
205+
log.error(f"An error occurred: {e}")
206+
sys.exit(1)
207+
208+
209+
def get_parser(args):
210+
"""
211+
Get the correct parser based on the input source.
212+
:param args: argparse.Namespace
213+
:type args: argparse.Namespace
214+
:return: MailParser
215+
:rtype: mailparser.core.MailParser
216+
"""
195217
if args.file:
196-
if args.outlook:
197-
log.debug("Analysis Outlook mail")
198-
parser = mailparser.parse_from_file_msg(args.file)
199-
else:
200-
parser = mailparser.parse_from_file(args.file)
218+
return parse_file(args)
201219
elif args.string:
202-
parser = mailparser.parse_from_string(args.string)
220+
log.debug("Start analysis by string mail")
221+
return mailparser.parse_from_string(args.string)
203222
elif args.stdin:
204-
if args.outlook:
205-
raise MailParserOutlookError("You can't use stdin with msg Outlook")
206-
parser = mailparser.parse_from_file_obj(sys.stdin)
207-
223+
return parse_stdin(args)
224+
else:
225+
raise ValueError("No input source provided")
226+
227+
228+
def parse_file(args):
229+
"""
230+
Parse the file based on the arguments provided.
231+
:param args: argparse.Namespace
232+
:type args: argparse.Namespace
233+
:return: MailParser
234+
:rtype: mailparser.core.MailParser
235+
"""
236+
log.debug("Start analysis by file mail")
237+
if args.outlook:
238+
log.debug("Start analysis by Outlook msg")
239+
return mailparser.parse_from_file_msg(args.file)
240+
else:
241+
log.debug("Start analysis by raw mail")
242+
return mailparser.parse_from_file(args.file)
243+
244+
245+
def parse_stdin(args):
246+
"""
247+
Parse the stdin based on the arguments provided.
248+
:param args: argparse.Namespace
249+
:type args: argparse.Namespace
250+
:return: MailParser
251+
:rtype: mailparser.core.MailParser
252+
"""
253+
log.debug("Start analysis by stdin mail")
254+
if args.outlook:
255+
raise MailParserOutlookError("You can't use stdin with msg Outlook")
256+
return mailparser.parse_from_file_obj(sys.stdin)
257+
258+
259+
def process_output(args, parser):
260+
"""
261+
Process the output based on the arguments provided.
262+
:param args: argparse.Namespace
263+
:type args: argparse.Namespace
264+
:param parser: MailParser
265+
:type parser: mailparser.core.MailParser
266+
:param log: logger
267+
:type log: logging.Logger
268+
"""
208269
if args.json:
209270
safe_print(parser.mail_json)
210271

@@ -230,21 +291,13 @@ def main():
230291
safe_print(parser.received_json)
231292

232293
if args.defects:
233-
log.debug("Printing defects")
234-
for i in parser.defects_categories:
235-
safe_print(i)
294+
print_defects(parser)
236295

237296
if args.senderip:
238-
log.debug("Printing sender IP")
239-
r = parser.get_server_ipaddress(args.senderip)
240-
if r:
241-
safe_print(r)
242-
else:
243-
safe_print("Not Found")
297+
print_sender_ip(parser, args)
244298

245299
if args.attachments or args.attachments_hash:
246-
log.debug("Printing attachments details")
247-
print_attachments(parser.attachments, args.attachments_hash)
300+
print_attachments_details(parser, args)
248301

249302
if args.mail_hash:
250303
log.debug("Printing also mail fingerprints")
@@ -255,5 +308,41 @@ def main():
255308
write_attachments(parser.attachments, args.attachments_path)
256309

257310

258-
if __name__ == "__main__":
311+
def print_defects(parser):
312+
"""
313+
Print email defects.
314+
:param parser: MailParser
315+
:type parser: mailparser.core.MailParser
316+
"""
317+
log.debug("Printing defects")
318+
for defect in parser.defects_categories:
319+
safe_print(defect)
320+
321+
322+
def print_sender_ip(parser, args):
323+
"""
324+
Print sender IP address.
325+
:param parser: MailParser
326+
:type parser: mailparser.core.MailParser
327+
:param args: argparse.Namespace
328+
:type args: argparse.Namespace
329+
"""
330+
log.debug("Printing sender IP")
331+
sender_ip = parser.get_server_ipaddress(args.senderip)
332+
safe_print(sender_ip if sender_ip else "Not Found")
333+
334+
335+
def print_attachments_details(parser, args):
336+
"""
337+
Print attachments details.
338+
:param parser: MailParser
339+
:type parser: mailparser.core.MailParser
340+
:param args: argparse.Namespace
341+
:type args: argparse.Namespace
342+
"""
343+
log.debug("Printing attachments details")
344+
print_attachments(parser.attachments, args.attachments_hash)
345+
346+
347+
if __name__ == "__main__": # pragma: no cover
259348
main()

0 commit comments

Comments
 (0)