diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..351333e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,150 @@ +name: Build + +on: + push: + +jobs: + package: + name: Python Package + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.11 + cache: pip + cache-dependency-path: | + **/pyproject.toml + **/requirements*.txt + + - name: Prepare Python env + run: | + python -m pip install -U pip setuptools wheel + + - name: Create build info + run: | + bash scripts/build-info.sh + + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Install package + run: | + pip install . + + - name: Build package sdist + run: | + python setup.py sdist + + - name: Build package bdist (wheel) + run: | + python setup.py bdist_wheel + + + docker: + name: Docker + runs-on: ubuntu-latest + + env: + PUBLIC_IMAGE_PREFIX: 'datastewardshipwizard' + DOCKER_IMAGE_NAME: 'smp-importer' + DOCKER_META_CONTEXT: '.' + DOCKER_META_FILE: 'Dockerfile' + DOCKER_META_PLATFORMS: 'linux/amd64,linux/arm64' + + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + + - name: Create build info + run: | + bash scripts/build-info.sh + + # TEST DOCKER IMAGE BUILD + - name: Docker meta [test] + id: meta-test + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.PUBLIC_IMAGE_PREFIX }}/${{ env.DOCKER_IMAGE_NAME }} + tags: | + type=sha + + - name: Docker build [test] + uses: docker/build-push-action@v4 + with: + context: ${{ env.DOCKER_META_CONTEXT }} + file: ${{ env.DOCKER_META_FILE }} + platforms: ${{ env.DOCKER_META_PLATFORMS }} + push: false + tags: ${{ steps.meta-test.outputs.tags }} + labels: ${{ steps.meta-test.outputs.labels }} + + # PREPARE + - name: Docker login [docker.io] + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} + + # DEVELOPMENT IMAGES + - name: Docker meta [dev] + id: meta-dev + if: github.event_name != 'pull_request' + uses: docker/metadata-action@v4 + with: + images: | + ${{ secrets.DOCKER_HUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }} + tags: | + type=ref,event=branch + + - name: Docker build+push [dev] + uses: docker/build-push-action@v4 + if: github.event_name != 'pull_request' && steps.meta-dev.outputs.tags != '' + with: + context: ${{ env.DOCKER_META_CONTEXT }} + file: ${{ env.DOCKER_META_FILE }} + platforms: ${{ env.DOCKER_META_PLATFORMS }} + push: true + tags: ${{ steps.meta-dev.outputs.tags }} + labels: ${{ steps.meta-dev.outputs.labels }} + + # PUBLIC IMAGES + - name: Docker meta [public] + id: meta-public + if: github.event_name != 'pull_request' + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.PUBLIC_IMAGE_PREFIX }}/${{ env.DOCKER_IMAGE_NAME }} + tags: | + type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }} + + - name: Docker build+push [public] + uses: docker/build-push-action@v4 + if: github.event_name != 'pull_request' && steps.meta-public.outputs.tags != '' + with: + context: ${{ env.DOCKER_META_CONTEXT }} + file: ${{ env.DOCKER_META_FILE }} + platforms: ${{ env.DOCKER_META_PLATFORMS }} + push: true + tags: ${{ steps.meta-public.outputs.tags }} + labels: ${{ steps.meta-public.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6af5c95 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM datastewardshipwizard/python-base:3.11-alpine as builder + +WORKDIR /app + +COPY . /app + +RUN python -m pip wheel --no-cache-dir --wheel-dir=/app/wheels -r /app/requirements.txt \ + && python -m pip wheel --no-cache-dir --no-deps --wheel-dir=/app/wheels /app + + +FROM datastewardshipwizard/python-base:3.11-alpine + +ENV PATH "/home/user/.local/bin:$PATH" + +# Setup non-root user +USER user + +# Prepare dirs +WORKDIR /home/user +RUN mkdir -p /home/user/data + +RUN pip install uvicorn + +# Install Python packages +COPY --from=builder --chown=user:user /app/wheels /home/user/wheels +RUN python -m pip install --user --no-cache --no-index /home/user/wheels/* \ + && rm -rf /home/user/wheels + +# Run +CMD ["uvicorn", "smp_importer:app", "--proxy-headers", "--forwarded-allow-ips=*", "--host", "0.0.0.0", "--port", "8000"] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..739e465 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +[build-system] +requires = ['setuptools'] +build-backend = 'setuptools.build_meta' + +[project] +name = 'smp-importer' +version = '0.1.0' +description = 'Importer of maSMPs for Software Management Wizard' +readme = 'README.md' +keywords = ['dsw', 'smp', 'masmp', 'import', 'mapping'] +license = { text = 'Apache License 2.0' } +authors = [ + { name = 'Marek Suchánek', email = 'marek.suchanek@ds-wizard.org' }, + { name = 'Vojtěch Knaisl', email = 'vojtech.knaisl@ds-wizard.org' } +] +classifiers = [ + 'Development Status :: 4 - Beta', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Topic :: Text Processing', + 'Topic :: Utilities', +] +requires-python = '>=3.10, <4' +dependencies = [ + 'fastapi', + 'Jinja2', +] + +[project.urls] +Homepage = 'https://smw.ds-wizard.org' +Repository = 'https://github.com/ds-wizard/smp-importer' + +[tool.setuptools] +zip-safe = false + +[tool.setuptools.packages.find] +namespaces = true +where = ['src'] + +[tool.setuptools.package-data] +'*' = ['*.css', '*.js', '*.j2', '*.png'] + +[tool.distutils.bdist_wheel] +universal = true diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..851d177 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +annotated-types==0.6.0 +anyio==3.7.1 +fastapi==0.104.1 +idna==3.4 +Jinja2==3.1.2 +MarkupSafe==2.1.3 +pydantic==2.4.2 +pydantic_core==2.10.1 +sniffio==1.3.0 +starlette==0.27.0 +typing_extensions==4.8.0 diff --git a/scripts/build-info.sh b/scripts/build-info.sh new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b908cbe --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +import setuptools + +setuptools.setup() diff --git a/src/smp_importer/__init__.py b/src/smp_importer/__init__.py new file mode 100644 index 0000000..8a1c803 --- /dev/null +++ b/src/smp_importer/__init__.py @@ -0,0 +1,3 @@ +from .app import app + +__all__ = ['app'] diff --git a/src/smp_importer/app.py b/src/smp_importer/app.py new file mode 100644 index 0000000..1b4c271 --- /dev/null +++ b/src/smp_importer/app.py @@ -0,0 +1,44 @@ +import logging +import os +import pathlib + +import fastapi +import fastapi.responses +import fastapi.staticfiles +import fastapi.templating + + +ROOT_DIR = pathlib.Path(__file__).parent +STATIC_DIR = ROOT_DIR / 'static' +TEMPLATES_DIR = ROOT_DIR / 'templates' +LOG = logging.getLogger(__name__) + + +app = fastapi.FastAPI() +app.mount( + path='/static', + app=fastapi.staticfiles.StaticFiles(directory=STATIC_DIR), + name='static', +) +templates = fastapi.templating.Jinja2Templates(directory=TEMPLATES_DIR) + + +@app.get('/', response_class=fastapi.responses.HTMLResponse) +async def get_index(request: fastapi.Request): + return templates.TemplateResponse( + name='index.html.j2', + context={ + 'request': request, + 'debug': os.environ.get('IMPORTER_DEBUG', '') == 'true', + 'development': os.environ.get('IMPORTER_DEV', '') == 'true', + }, + ) + + +@app.get('/api/import', response_class=fastapi.responses.JSONResponse) +async def list_fips(request: fastapi.Request): + try: + return fastapi.responses.JSONResponse(content={'text': 'Hello'}) + except Exception as e: + LOG.error(f'Error appeared: {str(e)}', exc_info=e) + raise fastapi.HTTPException(status_code=500) diff --git a/src/smp_importer/static/script.js b/src/smp_importer/static/script.js new file mode 100644 index 0000000..e69de29 diff --git a/src/smp_importer/static/style.css b/src/smp_importer/static/style.css new file mode 100644 index 0000000..e69de29 diff --git a/src/smp_importer/templates/index.html.j2 b/src/smp_importer/templates/index.html.j2 new file mode 100644 index 0000000..f7e4892 --- /dev/null +++ b/src/smp_importer/templates/index.html.j2 @@ -0,0 +1,42 @@ + + +
+ +