commit 9acde4bd9d61bfac92a5104fa11e5fe049f57eca Author: Scott Wallace Date: Mon May 2 13:44:10 2022 +0100 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 120000 index 0000000..3e4e48b --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.gitignore \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb4afd3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +.mypy_cache/ +.pyenv/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..0f0d7aa --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,44 @@ +# This file is a template, and might need editing before it works on your project. +# To contribute improvements to CI/CD templates, please follow the Development guide at: +# https://docs.gitlab.com/ee/development/cicd/templates.html +# This specific template is located at: +# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml + +# This is a sample GitLab CI/CD configuration file that should run without any modifications. +# It demonstrates a basic 3 stage CI/CD pipeline. Instead of real tests or scripts, +# it uses echo commands to simulate the pipeline execution. +# +# A pipeline is composed of independent jobs that run scripts, grouped into stages. +# Stages run in sequential order, but jobs within stages run in parallel. +# +# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages + +before_script: + - pip install -Ur requirements.txt + +stages: # List of stages for jobs, and their order of execution + - test + - build + +build-job: # This job runs in the build stage, which runs first. + stage: build + script: + - echo "Building the Docker image..." + - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER $CI_REGISTRY --password-stdin + - docker build -t $CI_REGISTRY_IMAGE . + - docker push $CI_REGISTRY_IMAGE + - echo "Build complete." + +unit-test-job: # This job runs in the test stage. + stage: test + script: + - echo "Running unit tests..." + - python3 -munittest discover + - echo "Testing complete" + +lint-test-job: # This job also runs in the test stage. + stage: test # It can run at the same time as unit-test-job (in parallel). + script: + - echo "Linting code..." + - pylint -j0 -v $(git ls-files '*.py') + - echo "No lint issues found." diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c0470ce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +# Build an intermediate image for the Python requirements +FROM python:3.9-slim-buster as intermediate + +# Install Git and Python requirements +RUN apt update && apt install -y build-essential +COPY requirements.txt . +RUN python -m pip install --user -r requirements.txt pip + +# Start from a fresh image +FROM python:3.9-slim-buster + +# Keeps Python from generating .pyc files in the container +ENV PYTHONDONTWRITEBYTECODE=1 + +# Turns off buffering for easier container logging +ENV PYTHONUNBUFFERED=1 + +RUN useradd -u 4000 -d /app appuser +WORKDIR /app + +# Copy over the Python requirements from the intermediate container +COPY --from=intermediate /root/.local /app/.local + +RUN chown -R appuser: /app + +USER appuser + +# Set the main execution command +ENTRYPOINT [".local/bin/waitress-serve", "main:app"] +# HEALTHCHECK --interval=5s --timeout=30s --start-period=5s --retries=3 CMD [ "/app/healthcheck.sh" ] +EXPOSE 8080/tcp + +# Copy in the code +COPY main.py . diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..0ab5d8b --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,9 @@ +--- +version: "3" +services: + ical_offset: + container_name: ical_offset + build: + context: . + image: ical_offset:latest + restart: unless-stopped diff --git a/main.py b/main.py new file mode 100644 index 0000000..9205976 --- /dev/null +++ b/main.py @@ -0,0 +1,59 @@ +""" +Take existing calendar and update it slightly +""" + +from datetime import datetime, time, timedelta + +import requests +from flask import Flask, Response, request +from icalendar import Calendar, Event # type: ignore[import] +from werkzeug.middleware.proxy_fix import ProxyFix + +app = Flask(__name__) +app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1) # type: ignore[assignment] + + +@app.route('/', methods=['get']) +def process() -> Response: + """ + Fetch ICS url and convert from Events to Todos + + Returns: + Response: ical response + """ + + # Fetch existing calendar + orig = requests.get(request.args['url']) + days = request.args.get('days', '0') + hours = request.args.get('hours', '0') + mins = request.args.get('mins', '0') + + orig_cal = Calendar.from_ical(orig.text) + cal = Calendar() + + cal.add('version', '2.0') + cal.add( + 'prodid', + '-//Scott Wallace//event2task//EN', + ) + + for component in orig_cal.subcomponents: + if isinstance(component, Event): + entry = Event() + entry.add('description', component['description']) + entry.add('dtstamp', component['dtstamp']) + # entry.add('dtstart', component['dtstart']) + entry.add( + 'dtstart', + datetime.combine(component.decoded('dtstart'), time(0)) + + timedelta(days=int(days), hours=int(hours), minutes=int(mins)), + ) + entry.add('summary', component['summary']) + entry.add('uid', component['uid']) + + cal.add_component(entry) + + return Response( + cal.to_ical().decode().replace('\\r\\n', '\n').strip(), + headers={'content-type': 'text/calendar; charset=UTF-8'}, + ) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..70d9b9c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +icalendar +flask +requests +waitress