Initial commit
This commit is contained in:
commit
9acde4bd9d
1
.dockerignore
Symbolic link
1
.dockerignore
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
.gitignore
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
__pycache__/
|
||||||
|
.mypy_cache/
|
||||||
|
.pyenv/
|
44
.gitlab-ci.yml
Normal file
44
.gitlab-ci.yml
Normal file
|
@ -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."
|
34
Dockerfile
Normal file
34
Dockerfile
Normal file
|
@ -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 .
|
9
docker-compose.yaml
Normal file
9
docker-compose.yaml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
ical_offset:
|
||||||
|
container_name: ical_offset
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
image: ical_offset:latest
|
||||||
|
restart: unless-stopped
|
59
main.py
Normal file
59
main.py
Normal file
|
@ -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'},
|
||||||
|
)
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
icalendar
|
||||||
|
flask
|
||||||
|
requests
|
||||||
|
waitress
|
Loading…
Reference in a new issue