diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index aad099f..ebe7322 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -44,9 +44,20 @@ jobs: e2e/repeat.sh kubectl apply --server-side -f https://github.com/kserve/kserve/releases/download/v0.14.0/kserve-cluster-resources.yaml kubectl patch configmap/inferenceservice-config -n kserve --type=strategic -p '{"data": {"deploy": "{\"defaultDeploymentMode\": \"RawDeployment\"}"}}' e2e/enable-modelcar.sh - - name: Load image in KinD for amd64 - run: | + - name: Run OLOT tutorial of README, and load image in KinD for amd64 + run: | + IMAGE_DIR=download + OCI_REGISTRY_SOURCE=quay.io/mmortari/hello-world-wait:latest + OCI_REGISTRY_DESTINATION=localhost:5001/nstestorg/modelcar + rm -rf $IMAGE_DIR + skopeo copy --multi-arch all docker://${OCI_REGISTRY_SOURCE} oci:${IMAGE_DIR}:latest + poetry run olot $IMAGE_DIR --modelcard tests/data/sample-model/README.md tests/data/sample-model/model.joblib + # copy modelcar from oci-layout to the local docker registry + skopeo copy --tls-verify=false --multi-arch all oci:${IMAGE_DIR}:latest docker://${OCI_REGISTRY_DESTINATION} + # make sure the annotation is present digest=$(skopeo inspect --tls-verify=false --raw docker://localhost:5001/nstestorg/modelcar | jq -r '.manifests[] | select(.platform.architecture == "amd64") | .digest') + skopeo inspect --tls-verify=false docker://${OCI_REGISTRY_DESTINATION}@$digest --raw | jq -e '.layers | last | has("annotations")' + # tag amd64 as modelcar:v1 and load image in KinD cluster skopeo copy --src-tls-verify=false docker://localhost:5001/nstestorg/modelcar@$digest docker-daemon:localhost:5001/nstestorg/modelcar:v1 kind load docker-image -n "kind" "localhost:5001/nstestorg/modelcar:v1" - name: Apply Isvc using Modelcar # since the enable modelcar restart controller pod, better guard the kubectl apply diff --git a/README.md b/README.md index e0afa51..5647294 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ skopeo copy --multi-arch all docker://${OCI_REGISTRY_SOURCE} oci:${IMAGE_DIR}:la # oras copy --to-oci-layout $OCI_REGISTRY_SOURCE ./${IMAGE_DIR}:latest # chmod +w ${IMAGE_DIR}/blobs/sha256/* -# Appends to the image found in `download` the files `model.joblib` and `README.md` -poetry run olot $IMAGE_DIR tests/data/sample-model/model.joblib tests/data/sample-model/README.md +# Appends to the image found in `download` the files `model.joblib` and as ModelCarD the `README.md` +poetry run olot $IMAGE_DIR --modelcard tests/data/sample-model/README.md tests/data/sample-model/model.joblib # Pushes the (updated) image found in `download` folder to the registry `quay.io/mmortari/demo20241208` with tag `latest` skopeo copy --multi-arch all oci:${IMAGE_DIR}:latest docker://${OCI_REGISTRY_DESTINATION} @@ -64,6 +64,10 @@ Cleanup your local image podman image rm quay.io/mmortari/demo20241208:latest ``` +### Dev notes + +If copying the resulting image to local filesystem oci-layout using skopeo, make sure to enable `--dest-oci-accept-uncompressed-layers` option. + ### As a Python Package Install the package diff --git a/olot/basics.py b/olot/basics.py index a6174ee..0894ec4 100644 --- a/olot/basics.py +++ b/olot/basics.py @@ -68,7 +68,9 @@ def oci_layers_on_top(ocilayout: typing.Union[str, os.PathLike], model_files: Li config_sha = mc_json_hash manifest.config.digest = "sha256:" + config_sha manifest.config.size = os.stat(ocilayout / "blobs" / "sha256" / config_sha).st_size - manifest.annotations["io.opendatahub.temp.author"] = "olot" # type:ignore + if manifest.annotations is None: + manifest.annotations = {} + manifest.annotations["io.opendatahub.temp.author"] = "olot" manifest_json = manifest.model_dump_json(exclude_none=True) with open(ocilayout / "blobs" / "sha256" / manifest_hash, "w") as cf: cf.write(manifest_json) diff --git a/olot/cli.py b/olot/cli.py index dfa707c..58e751a 100644 --- a/olot/cli.py +++ b/olot/cli.py @@ -1,3 +1,4 @@ +from os import PathLike from pathlib import Path import click @@ -7,7 +8,8 @@ @click.command() +@click.option("-m", "--modelcard", type=click.Path(exists=True, file_okay=True, dir_okay=False)) @click.argument('ocilayout', type=click.Path(exists=True, file_okay=False, dir_okay=True)) @click.argument('model_files', nargs=-1) -def cli(ocilayout: str, model_files): - oci_layers_on_top(Path(ocilayout), model_files) +def cli(ocilayout: str, modelcard: PathLike, model_files): + oci_layers_on_top(Path(ocilayout), model_files, modelcard)