From 3268b9c0dc66ebf545fd9528d18822b9334ca0b5 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Wed, 9 Oct 2024 21:17:04 -0700 Subject: [PATCH 1/2] Use latest hatch, with `hatch fmt` and `hatch test` Also, just use Python 3.11.x - not a specific version - in CI. Signed-off-by: Nic Cope --- .github/workflows/ci.yml | 11 ++++++----- README.md | 6 +++--- pyproject.toml | 17 +++-------------- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f44dba..2a852b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,8 @@ on: env: # Common versions - PYTHON_VERSION: '3.11.5' + PYTHON_VERSION: '3.11' + HATCH_VERSION: '1.12.0' DOCKER_BUILDX_VERSION: 'v0.11.2' # These environment variables are important to the Crossplane CLI install.sh @@ -47,10 +48,10 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Setup Hatch - run: pipx install hatch==1.7.0 + run: pipx install hatch==${{ env.HATCH_VERSION }} - name: Lint - run: hatch run lint:check + run: hatch fmt unit-test: runs-on: ubuntu-22.04 @@ -64,10 +65,10 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Setup Hatch - run: pipx install hatch==1.7.0 + run: pipx install hatch==${{ env.HATCH_VERSION }} - name: Run Unit Tests - run: hatch run test:unit + run: hatch test --all --randomize # 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 diff --git a/README.md b/README.md index 6516e1b..c661402 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,11 @@ CLI][cli] to build functions. # Run the code in development mode, for crossplane beta render hatch run development -# Lint the code - see pyproject.toml -hatch run lint:check +# Lint and format the code - see pyproject.toml +hatch fmt # Run unit tests - see tests/test_fn.py -hatch run test:unit +hatch test # Build the function's runtime image - see Dockerfile $ docker build . --tag=runtime diff --git a/pyproject.toml b/pyproject.toml index 2064987..596ac5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,21 +48,10 @@ dependencies = ["ipython==8.28.0"] [tool.hatch.envs.default.scripts] development = "python function/main.py --insecure --debug" -[tool.hatch.envs.lint] -type = "virtual" -detached = true -path = ".venv-lint" +# This special environment is used by hatch fmt. +[tool.hatch.envs.hatch-static-analysis] dependencies = ["ruff==0.6.9"] - -[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" +config-path = "none" # Disable Hatch's default Ruff config. [tool.ruff] target-version = "py311" From cf9045098151087110ac3d77a9385c6240a6e4e9 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Wed, 9 Oct 2024 21:33:57 -0700 Subject: [PATCH 2/2] Use a more illustrative default example Emitting a hello world result isn't very useful. Instead, show how to compose an MR with some fields derived from the XR and some from the input. Signed-off-by: Nic Cope --- example/composition.yaml | 2 +- function/fn.py | 20 +++++++---- .../template.fn.crossplane.io_inputs.yaml | 7 ++-- tests/test_fn.py | 35 ++++++++++++++----- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/example/composition.yaml b/example/composition.yaml index da197e7..09cdc9d 100644 --- a/example/composition.yaml +++ b/example/composition.yaml @@ -14,4 +14,4 @@ spec: input: apiVersion: template.fn.crossplane.io/v1beta1 kind: Input - example: "Hello world" + version: v1beta2 diff --git a/function/fn.py b/function/fn.py index 705121b..7cf8c25 100644 --- a/function/fn.py +++ b/function/fn.py @@ -1,7 +1,7 @@ """A Crossplane composition function.""" import grpc -from crossplane.function import logging, response +from crossplane.function import logging, resource, response from crossplane.function.proto.v1 import run_function_pb2 as fnv1 from crossplane.function.proto.v1 import run_function_pb2_grpc as grpcv1 @@ -22,12 +22,18 @@ class FunctionRunner(grpcv1.FunctionRunnerService): rsp = response.to(req) - example = "" - if "example" in req.input: - example = req.input["example"] + version = req.input["version"] + region = req.observed.composite.resource["spec"]["region"] - # TODO: Add your function logic here! - response.normal(rsp, f"I was run with input {example}!") - log.info("I was run!", input=example) + resource.update( + rsp.desired.resources["bucket"], + { + "apiVersion": f"s3.aws.upbound.io/{version}", + "kind": "Bucket", + "spec": { + "forProvider": {"region": region}, + }, + }, + ) return rsp diff --git a/package/input/template.fn.crossplane.io_inputs.yaml b/package/input/template.fn.crossplane.io_inputs.yaml index f10d927..7889880 100644 --- a/package/input/template.fn.crossplane.io_inputs.yaml +++ b/package/input/template.fn.crossplane.io_inputs.yaml @@ -24,9 +24,8 @@ spec: of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string - example: - description: Example is an example field. Replace it with whatever input - you need. :) + version: + description: The bucket version to compose (e.g. v1beta2). type: string kind: description: 'Kind is a string value representing the REST resource this @@ -36,7 +35,7 @@ spec: metadata: type: object required: - - example + - version type: object served: true storage: true diff --git a/tests/test_fn.py b/tests/test_fn.py index 0c111ee..35e5333 100644 --- a/tests/test_fn.py +++ b/tests/test_fn.py @@ -28,17 +28,36 @@ class TestFunctionRunner(unittest.IsolatedAsyncioTestCase): TestCase( reason="The function should return the input as a result.", req=fnv1.RunFunctionRequest( - input=resource.dict_to_struct({"example": "Hello, world"}) + input=resource.dict_to_struct({"version": "v1beta2"}), + observed=fnv1.State( + composite=fnv1.Resource( + resource=resource.dict_to_struct( + { + "apiVersion": "example.crossplane.io/v1", + "kind": "XR", + "spec": {"region": "us-west-2"}, + } + ), + ), + ), ), want=fnv1.RunFunctionResponse( meta=fnv1.ResponseMeta(ttl=durationpb.Duration(seconds=60)), - desired=fnv1.State(), - results=[ - fnv1.Result( - severity=fnv1.SEVERITY_NORMAL, - message="I was run with input Hello, world!", - ) - ], + desired=fnv1.State( + resources={ + "bucket": fnv1.Resource( + resource=resource.dict_to_struct( + { + "apiVersion": "s3.aws.upbound.io/v1beta2", + "kind": "Bucket", + "spec": { + "forProvider": {"region": "us-west-2"}, + }, + } + ), + ), + }, + ), context=structpb.Struct(), ), ),