Add function-template-python
Signed-off-by: Nic Cope <nicc@rk0n.org>
This commit is contained in:
parent
8688f44f26
commit
1d5fe6ad6d
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
name: Bug Report
|
||||
about: Help us diagnose and fix bugs in this Function
|
||||
labels: bug
|
||||
---
|
||||
<!--
|
||||
Thank you for helping to improve Crossplane!
|
||||
|
||||
Please be sure to search for open issues before raising a new one. We use issues
|
||||
for bug reports and feature requests. Please find us at https://slack.crossplane.io
|
||||
for questions, support, and discussion.
|
||||
-->
|
||||
|
||||
### What happened?
|
||||
<!--
|
||||
Please let us know what behaviour you expected and how this Function diverged
|
||||
from that behaviour.
|
||||
-->
|
||||
|
||||
|
||||
### How can we reproduce it?
|
||||
<!--
|
||||
Help us to reproduce your bug as succinctly and precisely as possible. Artifacts
|
||||
such as example manifests or a script that triggers the issue are highly
|
||||
appreciated!
|
||||
-->
|
||||
|
||||
### What environment did it happen in?
|
||||
Function version:
|
||||
|
||||
<!--
|
||||
Include at least the version or commit of Crossplane you were running. Consider
|
||||
also including your:
|
||||
|
||||
* Cloud provider or hardware configuration
|
||||
* Kubernetes version (use `kubectl version`)
|
||||
* Kubernetes distribution (e.g. Tectonic, GKE, OpenShift)
|
||||
* OS (e.g. from /etc/os-release)
|
||||
* Kernel (e.g. `uname -a`)
|
||||
-->
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
name: Feature Request
|
||||
about: Help us make this Function more useful
|
||||
labels: enhancement
|
||||
---
|
||||
<!--
|
||||
Thank you for helping to improve Crossplane!
|
||||
|
||||
Please be sure to search for open issues before raising a new one. We use issues
|
||||
for bug reports and feature requests. Please find us at https://slack.crossplane.io
|
||||
for questions, support, and discussion.
|
||||
-->
|
||||
|
||||
### What problem are you facing?
|
||||
<!--
|
||||
Please tell us a little about your use case - it's okay if it's hypothetical!
|
||||
Leading with this context helps frame the feature request so we can ensure we
|
||||
implement it sensibly.
|
||||
--->
|
||||
|
||||
### How could this Function help solve your problem?
|
||||
<!--
|
||||
Let us know how you think this Function could help with your use case.
|
||||
-->
|
|
@ -0,0 +1,30 @@
|
|||
<!--
|
||||
Thank you for helping to improve Crossplane!
|
||||
|
||||
Please read through https://git.io/fj2m9 if this is your first time opening a
|
||||
Crossplane pull request. Find us in https://slack.crossplane.io/messages/dev if
|
||||
you need any help contributing.
|
||||
-->
|
||||
|
||||
### Description of your changes
|
||||
|
||||
<!--
|
||||
Briefly describe what this pull request does, and how it is covered by tests.
|
||||
Be proactive - direct your reviewers' attention to anything that needs special
|
||||
consideration.
|
||||
|
||||
You MUST either [x] check or ~strikethrough~ every item in the checklist below.
|
||||
|
||||
We love pull requests that fix an open issue. If yours does, use the below line
|
||||
to indicate which issue it fixes, for example "Fixes #500".
|
||||
-->
|
||||
|
||||
Fixes #
|
||||
|
||||
I have:
|
||||
|
||||
- [ ] Read and followed Crossplane's [contribution process].
|
||||
- [ ] Added or updated unit tests for my change.
|
||||
|
||||
[contribution process]: https://git.io/fj2m9
|
||||
[docs]: https://docs.crossplane.io/contribute/contribute
|
|
@ -0,0 +1,166 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release-*
|
||||
pull_request: {}
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Package version (e.g. v0.1.0)
|
||||
required: false
|
||||
|
||||
env:
|
||||
# Common versions
|
||||
PYTHON_VERSION: '3.11.5'
|
||||
DOCKER_BUILDX_VERSION: 'v0.11.2'
|
||||
|
||||
# These environment variables are important to the Crossplane CLI install.sh
|
||||
# script. They determine what version it installs.
|
||||
XP_CHANNEL: master # TODO(negz): Pin to stable once v1.14 is released.
|
||||
XP_VERSION: current # TODO(negz): Pin to a version once v1.14 is released.
|
||||
|
||||
# This CI job will automatically push new builds to xpkg.upbound.io if the
|
||||
# XPKG_ACCESS_ID and XPKG_TOKEN secrets are set in the GitHub respository (or
|
||||
# organization) settings. Create a token at https://accounts.upbound.io.
|
||||
XPKG_ACCESS_ID: ${{ secrets.XPKG_ACCESS_ID }}
|
||||
|
||||
# The package to push, without a version tag. The default matches GitHub. For
|
||||
# example xpkg.upbound.io/crossplane/function-template-go.
|
||||
XPKG: xpkg.upbound.io/${{ github.repository}}
|
||||
|
||||
# The package version to push. The default is 0.0.0-gitsha.
|
||||
XPKG_VERSION: ${{ inputs.version }}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Hatch
|
||||
run: pipx install hatch==1.7.0
|
||||
|
||||
- name: Lint
|
||||
run: hatch run lint:check
|
||||
|
||||
unit-test:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Hatch
|
||||
run: pipx install hatch==1.7.0
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: hatch run test:unit
|
||||
|
||||
# We want to build most packages for the amd64 and arm64 architectures. To
|
||||
# speed this up we build single-platform packages in parallel. We then upload
|
||||
# those packages to GitHub as a build artifact. The push job downloads those
|
||||
# artifacts and pushes them as a single multi-platform package.
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
arch:
|
||||
- amd64
|
||||
- arm64
|
||||
steps:
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.DOCKER_BUILDX_VERSION }}
|
||||
install: true
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# We ask Docker to use GitHub Action's native caching support to speed up
|
||||
# the build, per https://docs.docker.com/build/cache/backends/gha/.
|
||||
- name: Build Runtime
|
||||
id: image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/${{ matrix.arch }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
target: image
|
||||
build-args:
|
||||
PYTHON_VERSION=${{ env.PYTHON_VERSION }}
|
||||
outputs: type=docker,dest=runtime-${{ matrix.arch }}.tar
|
||||
|
||||
- name: Setup the Crossplane CLI
|
||||
run: "curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh"
|
||||
|
||||
- name: Build Package
|
||||
run: ./crossplane xpkg build --package-file=${{ matrix.arch }}.xpkg --package-root=package/ --embed-runtime-image-tarball=runtime-${{ matrix.arch }}.tar
|
||||
|
||||
- name: Upload Single-Platform Package
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: packages
|
||||
path: "*.xpkg"
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
# This job downloads the single-platform packages built by the build job, and
|
||||
# pushes them as a multi-platform package. We only push the package it the
|
||||
# XPKG_ACCESS_ID and XPKG_TOKEN secrets were provided.
|
||||
push:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download Single-Platform Packages
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: packages
|
||||
path: .
|
||||
|
||||
- name: Setup the Crossplane CLI
|
||||
run: "curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh"
|
||||
|
||||
- name: Login to Upbound
|
||||
uses: docker/login-action@v3
|
||||
if: env.XPKG_ACCESS_ID != ''
|
||||
with:
|
||||
registry: xpkg.upbound.io
|
||||
username: ${{ secrets.XPKG_ACCESS_ID }}
|
||||
password: ${{ secrets.XPKG_TOKEN }}
|
||||
|
||||
# If a version wasn't explicitly passed as a workflow_dispatch input we
|
||||
# default to version v0.0.0-<git-commit-date>-<git-short-sha>, for example
|
||||
# v0.0.0-20231101115142-1091066df799. This is a simple implementation of
|
||||
# Go's pseudo-versions: https://go.dev/ref/mod#pseudo-versions.
|
||||
- name: Set Default Multi-Platform Package Version
|
||||
if: env.XPKG_VERSION == ''
|
||||
run: echo "XPKG_VERSION=v0.0.0-$(date -d@$(git show -s --format=%ct) +%Y%m%d%H%M%S)-$(git rev-parse --short=12 HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Push Multi-Platform Package to Upbound
|
||||
if: env.XPKG_ACCESS_ID != ''
|
||||
run: "./crossplane --verbose xpkg push --package-files $(echo *.xpkg|tr ' ' ,) ${{ env.XPKG }}:${{ env.XPKG_VERSION }}"
|
|
@ -0,0 +1,39 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
|
||||
# It's important that this is Debian 12 to match the distroless image.
|
||||
FROM debian:12-slim AS build
|
||||
|
||||
RUN --mount=type=cache,target=/var/lib/apt/lists \
|
||||
--mount=type=cache,target=/var/cache/apt \
|
||||
rm -f /etc/apt/apt.conf.d/docker-clean \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends --yes python3-venv git
|
||||
|
||||
# Don't write .pyc bytecode files. These speed up imports when the program is
|
||||
# loaded. There's no point doing that in a container where they'll never be
|
||||
# persisted across restarts.
|
||||
ENV PYTHONDONTWRITEBYTECODE=true
|
||||
|
||||
# Use Hatch to build a wheel. The build stage must do this in a venv because
|
||||
# Debian doesn't have a hatch package, and it won't let you install one globally
|
||||
# using pip.
|
||||
WORKDIR /build
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/root/.cache/pip \
|
||||
python3 -m venv /venv/build \
|
||||
&& /venv/build/bin/pip install hatch \
|
||||
&& /venv/build/bin/hatch build -t wheel /whl
|
||||
|
||||
# Create a fresh venv and install only the function wheel into it.
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
python3 -m venv /venv/fn \
|
||||
&& /venv/fn/bin/pip install /whl/*.whl
|
||||
|
||||
# Copy the function venv to our runtime stage. It's important that the path be
|
||||
# the same as in the build stage, to avoid shebang paths and symlinks breaking.
|
||||
FROM gcr.io/distroless/python3-debian12 AS image
|
||||
WORKDIR /
|
||||
COPY --from=build /venv/fn /venv/fn
|
||||
EXPOSE 9443
|
||||
USER nonroot:nonroot
|
||||
ENTRYPOINT ["/venv/fn/bin/function"]
|
39
README.md
39
README.md
|
@ -1,2 +1,39 @@
|
|||
# function-template-python
|
||||
A template for writing a composition function in Python
|
||||
[](https://github.com/crossplane/function-template-go/actions/workflows/ci.yml)
|
||||
|
||||
A template for writing a [composition function][functions] in [Python][python].
|
||||
|
||||
To learn how to use this template:
|
||||
|
||||
* [Learn about how composition functions work][functions]
|
||||
|
||||
If you just want to jump in and get started:
|
||||
|
||||
1. Replace `function-template-python` with your function's name in
|
||||
`pyproject.toml` and `package/crossplane.yaml`.
|
||||
1. Add your logic to `RunFunction` in `function/fn.py`
|
||||
1. Add tests for your logic in `test/test_fn.py`
|
||||
1. Update this file, `README.md`, to be about your function!
|
||||
|
||||
This template uses [Python][python], [Docker][docker], and the [Crossplane
|
||||
CLI][cli] to build functions.
|
||||
|
||||
```shell
|
||||
# Lint the code - see pyproject.toml
|
||||
hatch run lint:check
|
||||
|
||||
# Run unit tests - see tests/test_fn.py
|
||||
hatch run test:unit
|
||||
|
||||
# Build the function's runtime image - see Dockerfile
|
||||
$ docker build . --tag=runtime
|
||||
|
||||
# Build a function package - see package/crossplane.yaml
|
||||
$ crossplane xpkg build -f package --embed-runtime-image=runtime
|
||||
```
|
||||
|
||||
[functions]: https://docs.crossplane.io/latest/concepts/composition-functions
|
||||
[python]: https://python.io
|
||||
[package docs]: https://pkg.go.dev/github.com/crossplane/function-sdk-go
|
||||
[docker]: https://www.docker.com
|
||||
[cli]: https://docs.crossplane.io/latest/cli
|
|
@ -0,0 +1,25 @@
|
|||
# Example manifests
|
||||
|
||||
You can run your function locally and test it using `crossplane beta render`
|
||||
with these example manifests.
|
||||
|
||||
```shell
|
||||
# Run the function locally
|
||||
$ hatch run python function/main.py --insecure --debug
|
||||
```
|
||||
|
||||
```shell
|
||||
# Then, in another terminal, call it with these example manifests
|
||||
$ crossplane beta render xr.yaml composition.yaml functions.yaml -r
|
||||
---
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XR
|
||||
metadata:
|
||||
name: example-xr
|
||||
---
|
||||
apiVersion: render.crossplane.io/v1beta1
|
||||
kind: Result
|
||||
message: I was run with input "Hello world"!
|
||||
severity: SEVERITY_NORMAL
|
||||
step: run-the-template
|
||||
```
|
|
@ -0,0 +1,17 @@
|
|||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: function-template-python
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XR
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: run-the-template
|
||||
functionRef:
|
||||
name: function-template-python
|
||||
input:
|
||||
apiVersion: template.fn.crossplane.io/v1beta1
|
||||
kind: Input
|
||||
example: "Hello world"
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
apiVersion: pkg.crossplane.io/v1beta1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: function-template-python
|
||||
annotations:
|
||||
# This tells crossplane beta render to connect to the function locally.
|
||||
render.crossplane.io/runtime: Development
|
||||
spec:
|
||||
# This is ignored when using the Development runtime.
|
||||
package: function-template-python
|
|
@ -0,0 +1,7 @@
|
|||
# Replace this with your XR!
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XR
|
||||
metadata:
|
||||
name: example-xr
|
||||
spec:
|
||||
region: us-east-2
|
|
@ -0,0 +1,33 @@
|
|||
"""A Crossplane composition function."""
|
||||
|
||||
import grpc
|
||||
from crossplane.function import logging, response
|
||||
from crossplane.function.proto.v1beta1 import run_function_pb2 as fnv1beta1
|
||||
from crossplane.function.proto.v1beta1 import run_function_pb2_grpc as grpcv1beta1
|
||||
|
||||
|
||||
class FunctionRunner(grpcv1beta1.FunctionRunnerService):
|
||||
"""A FunctionRunner handles gRPC RunFunctionRequests."""
|
||||
|
||||
def __init__(self):
|
||||
"""Create a new FunctionRunner."""
|
||||
self.log = logging.get_logger()
|
||||
|
||||
async def RunFunction(
|
||||
self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext
|
||||
) -> fnv1beta1.RunFunctionResponse:
|
||||
"""Run the function."""
|
||||
log = self.log.bind(tag=req.meta.tag)
|
||||
log.info("Running function")
|
||||
|
||||
rsp = response.to(req)
|
||||
|
||||
example = ""
|
||||
if "example" in req.input:
|
||||
example = req.input["example"]
|
||||
|
||||
# TODO: Add your function logic here!
|
||||
response.normal(rsp, f"I was run with input {example}!")
|
||||
log.info("I was run!", input=example)
|
||||
|
||||
return rsp
|
|
@ -0,0 +1,51 @@
|
|||
"""The composition function's main CLI."""
|
||||
|
||||
import click
|
||||
from crossplane.function import logging, runtime
|
||||
|
||||
from function import fn
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"--debug",
|
||||
"-d",
|
||||
is_flag=True,
|
||||
help="Emit debug logs.",
|
||||
)
|
||||
@click.option(
|
||||
"--address",
|
||||
default="0.0.0.0:9443",
|
||||
show_default=True,
|
||||
help="Address at which to listen for gRPC connections",
|
||||
)
|
||||
@click.option(
|
||||
"--tls-certs-dir",
|
||||
help="Serve using mTLS certificates.",
|
||||
envvar="TLS_SERVER_CERTS_DIR",
|
||||
)
|
||||
@click.option(
|
||||
"--insecure",
|
||||
is_flag=True,
|
||||
help="Run without mTLS credentials. "
|
||||
"If you supply this flag --tls-certs-dir will be ignored.",
|
||||
)
|
||||
def cli(debug: bool, address: str, tls_certs_dir: str, insecure: bool) -> None: # noqa:FBT001 # We only expect callers via the CLI.
|
||||
"""A Crossplane composition function."""
|
||||
try:
|
||||
level = logging.Level.INFO
|
||||
if debug:
|
||||
level = logging.Level.DEBUG
|
||||
logging.configure(level=level)
|
||||
runtime.serve(
|
||||
fn.FunctionRunner(),
|
||||
address,
|
||||
creds=runtime.load_credentials(tls_certs_dir),
|
||||
insecure=insecure,
|
||||
)
|
||||
except Exception as e:
|
||||
click.echo(f"Cannot run function: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
apiVersion: meta.pkg.crossplane.io/v1beta1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: function-template-python
|
||||
spec: {}
|
|
@ -0,0 +1,116 @@
|
|||
[build-system]
|
||||
requires = ["hatchling", "hatch-vcs"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "function-template-python"
|
||||
description = 'A composition function'
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
license = "Apache-2.0"
|
||||
keywords = []
|
||||
authors = [{ name = "Crossplane Maintainers", email = "info@crossplane.io" }]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
# TODO(negz): Push to PyPI instead. We're currently waiting on our request
|
||||
# for a new PyPI org for Crossplane to be approved. Once we're using a PyPI
|
||||
# package (e.g. a wheel), confirm that run_function_pb2.pyi is installed. This
|
||||
# interface file makes working with RunFunctionRequest and RunFunctionResponse
|
||||
# a lot easier, since it contains all the type metadata.
|
||||
"function-sdk-python @ git+https://github.com/crossplane/function-sdk-python",
|
||||
|
||||
# Pin at least the things we import directly.
|
||||
"click==8.1.7",
|
||||
"grpcio==1.59.2",
|
||||
"protobuf==4.25.1",
|
||||
]
|
||||
|
||||
dynamic = ["version"]
|
||||
|
||||
[project.urls]
|
||||
Documentation = "https://github.com/crossplane/function-template-python#readme"
|
||||
Issues = "https://github.com/crossplane/function-template-python/issues"
|
||||
Source = "https://github.com/crossplane/function-template-python"
|
||||
|
||||
[project.scripts]
|
||||
function = "function.main:cli"
|
||||
|
||||
[tool.hatch.version]
|
||||
source = "vcs"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
[tool.hatch.envs.default]
|
||||
type = "virtual"
|
||||
path = ".venv-default"
|
||||
dependencies = ["ipython==8.17.2"]
|
||||
|
||||
[tool.hatch.envs.lint]
|
||||
type = "virtual"
|
||||
detached = true
|
||||
path = ".venv-lint"
|
||||
dependencies = ["ruff==0.1.6"]
|
||||
|
||||
[tool.hatch.envs.lint.scripts]
|
||||
check = "ruff check function tests && ruff format --diff function tests"
|
||||
|
||||
[tool.hatch.envs.test]
|
||||
type = "virtual"
|
||||
path = ".venv-test"
|
||||
|
||||
[tool.hatch.envs.test.scripts]
|
||||
unit = "python -m unittest tests/*.py"
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py311"
|
||||
exclude = ["function/proto/*"]
|
||||
select = [
|
||||
"A",
|
||||
"ARG",
|
||||
"ASYNC",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"DTZ",
|
||||
"E",
|
||||
"EM",
|
||||
"ERA",
|
||||
"F",
|
||||
"FBT",
|
||||
"I",
|
||||
"ICN",
|
||||
"ISC",
|
||||
"N",
|
||||
"PLC",
|
||||
"PLE",
|
||||
"PLR",
|
||||
"PLW",
|
||||
"Q",
|
||||
"RUF",
|
||||
"S",
|
||||
"T",
|
||||
"TID",
|
||||
"UP",
|
||||
"W",
|
||||
"YTT",
|
||||
]
|
||||
ignore = ["ISC001"] # Ruff warns this is incompatible with ruff format.
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tests/*" = ["D"] # Don't require docstrings for tests.
|
||||
|
||||
[tool.ruff.isort]
|
||||
known-first-party = ["function"]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
|
||||
[tool.ruff.lint.pep8-naming]
|
||||
# gRPC requires this PascalCase function name.
|
||||
extend-ignore-names = ["RunFunction"]
|
|
@ -0,0 +1,56 @@
|
|||
import dataclasses
|
||||
import unittest
|
||||
|
||||
from crossplane.function import logging, resource
|
||||
from crossplane.function.proto.v1beta1 import run_function_pb2 as fnv1beta1
|
||||
from google.protobuf import duration_pb2 as durationpb
|
||||
from google.protobuf import json_format
|
||||
from google.protobuf import struct_pb2 as structpb
|
||||
|
||||
from function import fn
|
||||
|
||||
|
||||
class TestFunctionRunner(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self) -> None:
|
||||
logging.configure(level=logging.Level.DISABLED)
|
||||
|
||||
async def test_run_function(self) -> None:
|
||||
@dataclasses.dataclass
|
||||
class TestCase:
|
||||
reason: str
|
||||
req: fnv1beta1.RunFunctionRequest
|
||||
want: fnv1beta1.RunFunctionResponse
|
||||
|
||||
cases = [
|
||||
TestCase(
|
||||
reason="The function should return the input as a result.",
|
||||
req=fnv1beta1.RunFunctionRequest(
|
||||
input=resource.dict_to_struct({"example": "Hello, world"})
|
||||
),
|
||||
want=fnv1beta1.RunFunctionResponse(
|
||||
meta=fnv1beta1.ResponseMeta(ttl=durationpb.Duration(seconds=60)),
|
||||
desired=fnv1beta1.State(),
|
||||
results=[
|
||||
fnv1beta1.Result(
|
||||
severity=fnv1beta1.SEVERITY_NORMAL,
|
||||
message="I was run with input Hello, world!",
|
||||
)
|
||||
],
|
||||
context=structpb.Struct(),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
runner = fn.FunctionRunner()
|
||||
|
||||
for case in cases:
|
||||
got = await runner.RunFunction(case.req, None)
|
||||
self.assertEqual(
|
||||
json_format.MessageToJson(case.want),
|
||||
json_format.MessageToJson(got),
|
||||
"-want, +got",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Reference in New Issue