Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.git
.env
.env.*
.venv
.hatch
dist
__pycache__
*.pyc
site
*.tif
29 changes: 29 additions & 0 deletions .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Processing model acceptance
on:
push:
branches: ['feature/**']
pull_request:
workflow_call:
permissions:
contents: read
jobs:
processing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: astral-sh/setup-uv@v6
- run: |
uv pip install --system --require-hashes -r requirements/development.txt
uv pip install --system --no-deps --no-build-isolation -e .
- run: hatch run dev:check
- run: hatch run test:test
- run: |
python scripts/prepare-inline.py --output /tmp/inline.cwl
cwltool --validate /tmp/inline.cwl
- run: |
docker build -t inline-processing:test .
docker run --rm inline-processing:test python -m app --help
docker run --rm inline-processing:test python -c 'import importlib.util,os; assert os.getuid()==os.getgid()==2000; assert importlib.util.find_spec("pip") is None'
182 changes: 50 additions & 132 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,136 +1,54 @@
name: build
name: Candidate publication
on:
push:
branches:
- main
paths:
- app.py
- .github/**
- docs/**
- workflow.cwl.template
- requirements.txt
- Dockerfile

workflow_dispatch:
inputs:
publish_candidate:
description: Publish candidate image after processing checks
type: boolean
default: false
permissions:
contents: read
jobs:
python-unit-test:

validate:
uses: ./.github/workflows/acceptance.yml
candidate:
needs: validate
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.x
- run: pip install -r requirements.txt
- run: pip install nose2
- run: nose2

cwl-pack-validate:
needs: python-unit-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.x
- run: pip install cwltool
- run: content=`cat app.py` yq eval '.$graph[1].requirements.InitialWorkDirRequirement.listing[0].entry=strenv(content)' workflow.cwl.template > workflow.cwl
- run: cwltool --validate workflow.cwl

version:
needs: cwl-pack-validate

runs-on: ubuntu-latest

outputs:

app-version: ${{ steps.set-version.outputs.version }}

steps:
- uses: actions/checkout@v2
- run: echo "APP_VERSION=$(cat codemeta.json | jq -r .version )" >> $GITHUB_ENV
- run: echo app version is $APP_VERSION
- id: set-version
run: echo "::set-output name=version::$APP_VERSION"

container-build:

needs: version

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- run: echo version ${{needs.version.outputs.app-version}}
- run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: build & push image
run: |
IMAGE_ID=ghcr.io/eoap/quickwin-inline-code/python-inline-code
docker build . --file Dockerfile --tag $IMAGE_ID:${{needs.version.outputs.app-version}}
docker push $IMAGE_ID:${{needs.version.outputs.app-version}}

create-release:

needs:
- container-build
- version

runs-on: ubuntu-latest

outputs:

upload_url: ${{ steps.set-upload-url.outputs.upload_url }}

steps:
- name: release
uses: actions/create-release@v1
id: create_release
with:
draft: false
prerelease: false
release_name: ${{needs.version.outputs.app-version}}
tag_name: ${{needs.version.outputs.app-version}}
env:
GITHUB_TOKEN: ${{ github.token }}
- id: set-upload-url
run: echo "::set-output name=upload_url::${{ steps.create_release.outputs.upload_url }}"

publish-artifacts:

needs:
- create-release
- version

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- run: content=`cat app.py` yq eval '.$graph[1].requirements.InitialWorkDirRequirement.listing[0].entry=strenv(content)' workflow.cwl.template > workflow.cwl
- run: |
tag="ghcr.io/eoap/quickwin-inline-code/python-inline-code:${{needs.version.outputs.app-version}}"
docker pull ${tag}
shatag=$( docker inspect ${tag} | yq -r '.[0]["RepoDigests"][0]' )
s="detect-water-body" t="${shatag}" yq -i eval '(.$graph[] | select (.id == env(s)) ).hints.DockerRequirement.dockerPull = env(t)' workflow.cwl

- run: |
r=$( cat codemeta.json | jq -r ".codeRepository" ) yq -i eval '."s:codeRepository" = {"URL" : env(r)}' workflow.cwl
v="${{needs.version.outputs.app-version}}" yq -i eval '."s:softwareVersion" = env(v)' workflow.cwl
n=$(cat codemeta.json | jq -r '(.author[0].givenName + " " + .author[0].familyName)') \
e=$(cat codemeta.json | jq -r '.author[0].email') \
a=$(cat codemeta.json | jq -r '.author[0].affiliation["name"]') \
yq eval -i '."s:author" += [{"class": "s:Person", "s.name": env(n), "s.email": env(e), "s.affiliation": env(a)}]' workflow.cwl
- run: |
mkdir downloads
cp workflow.cwl downloads/app-water-body-inline-code.${{needs.version.outputs.app-version}}.cwl
- uses: actions/upload-artifact@v4
with:
name: application-package
path: downloads
- name: upload linux artifact 1
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: ${{needs.create-release.outputs.upload_url}}
asset_path: downloads/app-water-body-inline-code.${{needs.version.outputs.app-version}}.cwl
asset_name: app-water-body-inline-code.${{needs.version.outputs.app-version}}.cwl
asset_content_type: text/yaml
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: astral-sh/setup-uv@v6
- run: uv pip install --system --require-hashes -r requirements/development.txt
- name: Build the intended runtime
run: |
docker build -t ghcr.io/eoap/quickwin-inline-code/python-inline-code:2.0.0-rc.${{ github.run_number }} .
docker run --rm ghcr.io/eoap/quickwin-inline-code/python-inline-code:2.0.0-rc.${{ github.run_number }} python -m app --help
- uses: docker/login-action@v3
if: inputs.publish_candidate
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish candidate and prepare immutable CWL
if: inputs.publish_candidate
run: |
image=ghcr.io/eoap/quickwin-inline-code/python-inline-code:2.0.0-rc.${{ github.run_number }}
docker push "$image"
published=$(docker inspect "$image" --format '{{index .RepoDigests 0}}')
mkdir candidate
python scripts/prepare-inline.py --image "$published" --version 2.0.0-rc.${{ github.run_number }} --output candidate/app-water-body-inline-code.cwl
cwltool --validate candidate/app-water-body-inline-code.cwl
cd candidate
sha256sum app-water-body-inline-code.cwl > SHA256SUMS
- uses: actions/upload-artifact@v4
if: inputs.publish_candidate
with:
name: inline-code-candidate
path: candidate
# Stable promotion requires recorded real-data and immutable-artifact acceptance,
# resolved license metadata, and explicit release authorization.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,12 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

.venv/
.hatch/
dist/
__pycache__/
*.pyc
.pytest_cache/
.mypy_cache/
.ruff_cache/
42 changes: 34 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
FROM docker.io/python:slim-bullseye
FROM docker.io/rockylinux/rockylinux:10.1-minimal@sha256:cfa2812ec7151c2be393cfe46e50cf59426373d12f03f4ffbdd56becb8195a65 AS builder
RUN microdnf -y update && \
microdnf -y install curl tar git python3 python3-pip python3-setuptools gcc gcc-c++ make sqlite-devel zlib-devel glibc-devel libstdc++-devel && \
microdnf clean all
RUN test "$(uname -m)" = x86_64 && \
curl -fsSL https://github.com/pypa/hatch/releases/download/hatch-v1.14.0/hatch-x86_64-unknown-linux-gnu.tar.gz -o /tmp/hatch.tar.gz && \
echo "33af9b6f72cf11db6bc96070901cb9d32adbe316534bbf41d345cdd0d84b3459 /tmp/hatch.tar.gz" | sha256sum -c - && \
tar -xzf /tmp/hatch.tar.gz -C /tmp && \
install -m 0755 /tmp/hatch /usr/local/bin/hatch && \
rm -rf /tmp/hatch*
WORKDIR /src
COPY . /src
RUN rm -rf dist && hatch build -t wheel

ADD requirements.txt /tmp/requirements.txt

RUN pip install --no-cache-dir -r /tmp/requirements.txt && \
python -c "import rasterio" && \
rm -rf /tmp/requirements.txt

ENTRYPOINT []
FROM docker.io/rockylinux/rockylinux:10.1-minimal@sha256:cfa2812ec7151c2be393cfe46e50cf59426373d12f03f4ffbdd56becb8195a65 AS runtime
RUN microdnf -y update && \
microdnf -y install python3 python3-pip git jq libstdc++ sqlite-libs zlib libgomp expat && \
microdnf clean all
ENV HOME=/home/neo
RUN /usr/sbin/groupadd -g 2000 neo && \
/usr/sbin/useradd -u 2000 -g 2000 -m -d ${HOME} -s /sbin/nologin neo && \
mkdir -p /app /work && chown -R 2000:2000 /app /work ${HOME}
USER neo
WORKDIR /app
ENV VIRTUAL_ENV=/app/venv
ENV PATH="${VIRTUAL_ENV}/bin:${PATH}:/usr/bin"
RUN python3 -c 'import sys; assert sys.version_info[:2] == (3, 12)' && python3 -m venv /app/venv
COPY --chown=2000:2000 requirements/runtime.txt /app/requirements.txt
RUN pip install --no-cache-dir --require-hashes -r /app/requirements.txt
COPY --from=builder --chown=2000:2000 /src/dist/*.whl /app/dist/
RUN pip install --no-cache-dir --no-deps /app/dist/*.whl && \
pip check && python -m pip uninstall --yes pip && detect-water-body-inline --help && python -m app --help && \
rm -rf /app/requirements.txt /app/dist
ENTRYPOINT []
CMD ["detect-water-body-inline", "--help"]
43 changes: 30 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,15 @@ The CWL document generated is available in the [releases section](https://github

Update the Application Package template `workflow.cwl.template` with the Python `app.py` as the inline code to run:

```bash
python scripts/prepare-inline.py --output workflow.cwl
```
content=`cat app.py` yq eval '.$graph[1].requirements.InitialWorkDirRequirement.listing[0].entry=strenv(content)' workflow.cwl.template > workflow.cwl
```

Here's a breakdown of the command:

* `cat app.py`: This command outputs the contents of the `app.py` file.

* `yq eval '.$graph[1].requirements.InitialWorkDirRequirement.listing[0].entry=strenv(content)' workflow.cwl.template`: This command uses yq to evaluate a YAML expression on the `workflow.cwl.template` file. The expression modifies the entry at a specific path in the CWL `InitialWorkDirRequirement`, setting it to the content of `app.py`.

* `> workflow.cwl`: This part of the command redirects the modified output to a new file named `workflow.cwl`.
The generated CWL v1.2 document embeds the complete standalone `app.py`, retains
the published URI schema import, and includes its module-owned types inline.
It accepts a URI record, named bounding-box coordinates and quoted CRS code
`"4326"`; the command converts the code to `EPSG:4326` at the CLI boundary.
Use `--image <local-tag-or-published-digest>` to test the intended image.

## Run the Application Package

Expand All @@ -37,9 +35,17 @@ cwltool workflow.cwl params.yaml
Where `params.yaml` contains:

```yaml
item: "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/S2B_10TFK_20210713_0_L2A"
aoi: "-121.399,39.834,-120.74,40.472"
epsg: "EPSG:4326"
item:
value: https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/S2B_10TFK_20210713_0_L2A
aoi:
west: -121.399
south: 39.834
east: -120.74
north: 40.472
epsg: '4326'
bands:
- green
- nir
```

## Run the released Application Package
Expand Down Expand Up @@ -97,4 +103,15 @@ The key components of this CWL document are:

The webpage of the documentation is https://eoap.github.io/quickwin-inline-code/.

[![License: CC BY-SA 4.0](https://img.shields.io/badge/License-CC_BY--SA_4.0-lightgrey.svg)](https://creativecommons.org/licenses/by-sa/4.0/)
[![License: CC BY-SA 4.0](https://img.shields.io/badge/License-CC_BY--SA_4.0-lightgrey.svg)](https://creativecommons.org/licenses/by-sa/4.0/)
## Package and runtime checks

Python 3.12 is required. Run `hatch run dev:check` and `hatch run test:test`
for hash-locked quality and processing regressions. The wheel includes the same
standalone `app.py` embedded in CWL, so inline execution remains supported.
The two-stage Rocky 10.1 image installs the wheel and locked runtime, runs as
`neo` (UID/GID 2000), verifies dependencies and removes venv pip.

The next major version is 2.0.0. Stable promotion still requires accepted immutable image/CWL
artifacts and review of the existing license/author metadata: the checked-in
license says CC BY-SA while CodeMeta currently says CC BY-NC-SA.
Loading
Loading