function.response
Utilities for working with RunFunctionResponses.
1# Copyright 2023 The Crossplane Authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Utilities for working with RunFunctionResponses.""" 16 17import datetime 18 19from google.protobuf import duration_pb2 as durationpb 20from google.protobuf import struct_pb2 as structpb 21 22import crossplane.function.proto.v1.run_function_pb2 as fnv1 23from crossplane.function import resource 24 25"""The default TTL for which a RunFunctionResponse may be cached.""" 26DEFAULT_TTL = datetime.timedelta(minutes=1) 27 28_default_ttl = DEFAULT_TTL 29 30 31def get_default_ttl() -> datetime.timedelta: 32 """Return the process-wide default TTL for RunFunctionResponses.""" 33 return _default_ttl 34 35 36def set_default_ttl(ttl: datetime.timedelta) -> None: 37 """Set the process-wide default TTL for RunFunctionResponses. 38 39 Args: 40 ttl: How long Crossplane may optionally cache responses when no explicit 41 TTL is passed to :func:`to`. 42 """ 43 global _default_ttl # noqa: PLW0603 44 _default_ttl = ttl 45 46 47def to( 48 req: fnv1.RunFunctionRequest, 49 ttl: datetime.timedelta | None = None, 50) -> fnv1.RunFunctionResponse: 51 """Create a response to the supplied request. 52 53 Args: 54 req: The request to respond to. 55 ttl: How long Crossplane may optionally cache the response. Defaults to 56 the process-wide default TTL set by :func:`set_default_ttl`. 57 58 Returns: 59 A response to the supplied request. 60 61 The request's tag, desired resources, and context is automatically copied to 62 the response. Using response.to is a good pattern to ensure 63 """ 64 if ttl is None: 65 ttl = get_default_ttl() 66 67 dttl = durationpb.Duration() 68 dttl.FromTimedelta(ttl) 69 return fnv1.RunFunctionResponse( 70 meta=fnv1.ResponseMeta(tag=req.meta.tag, ttl=dttl), 71 desired=req.desired, 72 context=req.context, 73 ) 74 75 76def normal(rsp: fnv1.RunFunctionResponse, message: str) -> None: 77 """Add a normal result to the response.""" 78 rsp.results.append( 79 fnv1.Result( 80 severity=fnv1.SEVERITY_NORMAL, 81 message=message, 82 ) 83 ) 84 85 86def warning(rsp: fnv1.RunFunctionResponse, message: str) -> None: 87 """Add a warning result to the response.""" 88 rsp.results.append( 89 fnv1.Result( 90 severity=fnv1.SEVERITY_WARNING, 91 message=message, 92 ) 93 ) 94 95 96def fatal(rsp: fnv1.RunFunctionResponse, message: str) -> None: 97 """Add a fatal result to the response.""" 98 rsp.results.append( 99 fnv1.Result( 100 severity=fnv1.SEVERITY_FATAL, 101 message=message, 102 ) 103 ) 104 105 106_STATUS_MAP = { 107 "True": fnv1.STATUS_CONDITION_TRUE, 108 "False": fnv1.STATUS_CONDITION_FALSE, 109 "Unknown": fnv1.STATUS_CONDITION_UNKNOWN, 110} 111 112 113def set_conditions( 114 rsp: fnv1.RunFunctionResponse, 115 *conditions: resource.Condition, 116) -> None: 117 """Set one or more conditions on the composite resource (XR). 118 119 Args: 120 rsp: The RunFunctionResponse to update. 121 *conditions: The conditions to set. 122 123 Each condition is appended to ``rsp.conditions``. Crossplane uses the 124 conditions returned by a function to set custom status conditions on 125 the composite resource. 126 127 The ``last_transition_time`` field of each condition is ignored. 128 Crossplane sets the transition time itself. 129 130 Do not set the ``Ready`` condition type. Crossplane manages it based 131 on resource readiness. 132 """ 133 for condition in conditions: 134 c = fnv1.Condition( 135 type=condition.typ, 136 status=_STATUS_MAP.get(condition.status, fnv1.STATUS_CONDITION_UNKNOWN), 137 reason=condition.reason or "", 138 ) 139 if condition.message: 140 c.message = condition.message 141 rsp.conditions.append(c) 142 143 144def set_output(rsp: fnv1.RunFunctionResponse, output: dict | structpb.Struct) -> None: 145 """Set the output field in a RunFunctionResponse for operation functions. 146 147 Args: 148 rsp: The RunFunctionResponse to update. 149 output: The output data as a dictionary or protobuf Struct. 150 151 Operation functions can return arbitrary output data that will be written 152 to the Operation's status.pipeline field. This function sets that output 153 on the response. 154 """ 155 match output: 156 case dict(): 157 rsp.output.CopyFrom(resource.dict_to_struct(output)) 158 case structpb.Struct(): 159 rsp.output.CopyFrom(output) 160 case _: 161 t = type(output) 162 msg = f"Unsupported output type: {t}" 163 raise TypeError(msg) 164 165 166def require_resources( # noqa: PLR0913 167 rsp: fnv1.RunFunctionResponse, 168 name: str, 169 api_version: str, 170 kind: str, 171 *, 172 match_name: str | None = None, 173 match_labels: dict[str, str] | None = None, 174 namespace: str | None = None, 175) -> None: 176 """Add a resource requirement to the response. 177 178 Args: 179 rsp: The RunFunctionResponse to update. 180 name: The name to use for this requirement. 181 api_version: The API version of resources to require. 182 kind: The kind of resources to require. 183 match_name: Match a resource by name (mutually exclusive with match_labels). 184 match_labels: Match resources by labels (mutually exclusive with match_name). 185 namespace: The namespace to search in (optional). 186 187 Raises: 188 ValueError: If both match_name and match_labels are provided. 189 190 This tells Crossplane to fetch the specified resources and include them 191 in the next call to the function in req.required_resources[name]. 192 193 If neither match_name nor match_labels is provided, all resources of the 194 given api_version and kind are matched. 195 """ 196 if match_name is not None and match_labels is not None: 197 msg = "match_name and match_labels are mutually exclusive" 198 raise ValueError(msg) 199 200 selector = fnv1.ResourceSelector( 201 api_version=api_version, 202 kind=kind, 203 ) 204 205 if match_name is not None: 206 selector.match_name = match_name 207 208 if match_labels is not None: 209 selector.match_labels.labels.update(match_labels) 210 211 if namespace is not None: 212 selector.namespace = namespace 213 214 rsp.requirements.resources[name].CopyFrom(selector) 215 216 217def require_schema( 218 rsp: fnv1.RunFunctionResponse, 219 name: str, 220 api_version: str, 221 kind: str, 222) -> None: 223 """Add a schema requirement to the response. 224 225 Args: 226 rsp: The RunFunctionResponse to update. 227 name: The name to use for this requirement. 228 api_version: The API version of the resource kind, e.g. "example.org/v1". 229 kind: The kind of resource, e.g. "MyResource". 230 231 This tells Crossplane to fetch the OpenAPI schema for the specified resource 232 kind and include it in the next call to the function in 233 req.required_schemas[name]. Use request.get_required_schema to retrieve it. 234 235 For CRDs, Crossplane returns the spec.versions[].schema.openAPIV3Schema field. 236 If Crossplane cannot find a schema for the requested kind, the schema will be 237 empty (get_required_schema will return None). 238 """ 239 selector = fnv1.SchemaSelector( 240 api_version=api_version, 241 kind=kind, 242 ) 243 rsp.requirements.schemas[name].CopyFrom(selector)
32def get_default_ttl() -> datetime.timedelta: 33 """Return the process-wide default TTL for RunFunctionResponses.""" 34 return _default_ttl
Return the process-wide default TTL for RunFunctionResponses.
37def set_default_ttl(ttl: datetime.timedelta) -> None: 38 """Set the process-wide default TTL for RunFunctionResponses. 39 40 Args: 41 ttl: How long Crossplane may optionally cache responses when no explicit 42 TTL is passed to :func:`to`. 43 """ 44 global _default_ttl # noqa: PLW0603 45 _default_ttl = ttl
Set the process-wide default TTL for RunFunctionResponses.
Arguments:
- ttl: How long Crossplane may optionally cache responses when no explicit
TTL is passed to
to().
48def to( 49 req: fnv1.RunFunctionRequest, 50 ttl: datetime.timedelta | None = None, 51) -> fnv1.RunFunctionResponse: 52 """Create a response to the supplied request. 53 54 Args: 55 req: The request to respond to. 56 ttl: How long Crossplane may optionally cache the response. Defaults to 57 the process-wide default TTL set by :func:`set_default_ttl`. 58 59 Returns: 60 A response to the supplied request. 61 62 The request's tag, desired resources, and context is automatically copied to 63 the response. Using response.to is a good pattern to ensure 64 """ 65 if ttl is None: 66 ttl = get_default_ttl() 67 68 dttl = durationpb.Duration() 69 dttl.FromTimedelta(ttl) 70 return fnv1.RunFunctionResponse( 71 meta=fnv1.ResponseMeta(tag=req.meta.tag, ttl=dttl), 72 desired=req.desired, 73 context=req.context, 74 )
Create a response to the supplied request.
Arguments:
- req: The request to respond to.
- ttl: How long Crossplane may optionally cache the response. Defaults to
the process-wide default TTL set by
set_default_ttl().
Returns:
A response to the supplied request.
The request's tag, desired resources, and context is automatically copied to the response. Using response.to is a good pattern to ensure
77def normal(rsp: fnv1.RunFunctionResponse, message: str) -> None: 78 """Add a normal result to the response.""" 79 rsp.results.append( 80 fnv1.Result( 81 severity=fnv1.SEVERITY_NORMAL, 82 message=message, 83 ) 84 )
Add a normal result to the response.
87def warning(rsp: fnv1.RunFunctionResponse, message: str) -> None: 88 """Add a warning result to the response.""" 89 rsp.results.append( 90 fnv1.Result( 91 severity=fnv1.SEVERITY_WARNING, 92 message=message, 93 ) 94 )
Add a warning result to the response.
97def fatal(rsp: fnv1.RunFunctionResponse, message: str) -> None: 98 """Add a fatal result to the response.""" 99 rsp.results.append( 100 fnv1.Result( 101 severity=fnv1.SEVERITY_FATAL, 102 message=message, 103 ) 104 )
Add a fatal result to the response.
114def set_conditions( 115 rsp: fnv1.RunFunctionResponse, 116 *conditions: resource.Condition, 117) -> None: 118 """Set one or more conditions on the composite resource (XR). 119 120 Args: 121 rsp: The RunFunctionResponse to update. 122 *conditions: The conditions to set. 123 124 Each condition is appended to ``rsp.conditions``. Crossplane uses the 125 conditions returned by a function to set custom status conditions on 126 the composite resource. 127 128 The ``last_transition_time`` field of each condition is ignored. 129 Crossplane sets the transition time itself. 130 131 Do not set the ``Ready`` condition type. Crossplane manages it based 132 on resource readiness. 133 """ 134 for condition in conditions: 135 c = fnv1.Condition( 136 type=condition.typ, 137 status=_STATUS_MAP.get(condition.status, fnv1.STATUS_CONDITION_UNKNOWN), 138 reason=condition.reason or "", 139 ) 140 if condition.message: 141 c.message = condition.message 142 rsp.conditions.append(c)
Set one or more conditions on the composite resource (XR).
Arguments:
- rsp: The RunFunctionResponse to update.
- *conditions: The conditions to set.
Each condition is appended to rsp.conditions. Crossplane uses the
conditions returned by a function to set custom status conditions on
the composite resource.
The last_transition_time field of each condition is ignored.
Crossplane sets the transition time itself.
Do not set the Ready condition type. Crossplane manages it based
on resource readiness.
145def set_output(rsp: fnv1.RunFunctionResponse, output: dict | structpb.Struct) -> None: 146 """Set the output field in a RunFunctionResponse for operation functions. 147 148 Args: 149 rsp: The RunFunctionResponse to update. 150 output: The output data as a dictionary or protobuf Struct. 151 152 Operation functions can return arbitrary output data that will be written 153 to the Operation's status.pipeline field. This function sets that output 154 on the response. 155 """ 156 match output: 157 case dict(): 158 rsp.output.CopyFrom(resource.dict_to_struct(output)) 159 case structpb.Struct(): 160 rsp.output.CopyFrom(output) 161 case _: 162 t = type(output) 163 msg = f"Unsupported output type: {t}" 164 raise TypeError(msg)
Set the output field in a RunFunctionResponse for operation functions.
Arguments:
- rsp: The RunFunctionResponse to update.
- output: The output data as a dictionary or protobuf Struct.
Operation functions can return arbitrary output data that will be written to the Operation's status.pipeline field. This function sets that output on the response.
167def require_resources( # noqa: PLR0913 168 rsp: fnv1.RunFunctionResponse, 169 name: str, 170 api_version: str, 171 kind: str, 172 *, 173 match_name: str | None = None, 174 match_labels: dict[str, str] | None = None, 175 namespace: str | None = None, 176) -> None: 177 """Add a resource requirement to the response. 178 179 Args: 180 rsp: The RunFunctionResponse to update. 181 name: The name to use for this requirement. 182 api_version: The API version of resources to require. 183 kind: The kind of resources to require. 184 match_name: Match a resource by name (mutually exclusive with match_labels). 185 match_labels: Match resources by labels (mutually exclusive with match_name). 186 namespace: The namespace to search in (optional). 187 188 Raises: 189 ValueError: If both match_name and match_labels are provided. 190 191 This tells Crossplane to fetch the specified resources and include them 192 in the next call to the function in req.required_resources[name]. 193 194 If neither match_name nor match_labels is provided, all resources of the 195 given api_version and kind are matched. 196 """ 197 if match_name is not None and match_labels is not None: 198 msg = "match_name and match_labels are mutually exclusive" 199 raise ValueError(msg) 200 201 selector = fnv1.ResourceSelector( 202 api_version=api_version, 203 kind=kind, 204 ) 205 206 if match_name is not None: 207 selector.match_name = match_name 208 209 if match_labels is not None: 210 selector.match_labels.labels.update(match_labels) 211 212 if namespace is not None: 213 selector.namespace = namespace 214 215 rsp.requirements.resources[name].CopyFrom(selector)
Add a resource requirement to the response.
Arguments:
- rsp: The RunFunctionResponse to update.
- name: The name to use for this requirement.
- api_version: The API version of resources to require.
- kind: The kind of resources to require.
- match_name: Match a resource by name (mutually exclusive with match_labels).
- match_labels: Match resources by labels (mutually exclusive with match_name).
- namespace: The namespace to search in (optional).
Raises:
- ValueError: If both match_name and match_labels are provided.
This tells Crossplane to fetch the specified resources and include them in the next call to the function in req.required_resources[name].
If neither match_name nor match_labels is provided, all resources of the given api_version and kind are matched.
218def require_schema( 219 rsp: fnv1.RunFunctionResponse, 220 name: str, 221 api_version: str, 222 kind: str, 223) -> None: 224 """Add a schema requirement to the response. 225 226 Args: 227 rsp: The RunFunctionResponse to update. 228 name: The name to use for this requirement. 229 api_version: The API version of the resource kind, e.g. "example.org/v1". 230 kind: The kind of resource, e.g. "MyResource". 231 232 This tells Crossplane to fetch the OpenAPI schema for the specified resource 233 kind and include it in the next call to the function in 234 req.required_schemas[name]. Use request.get_required_schema to retrieve it. 235 236 For CRDs, Crossplane returns the spec.versions[].schema.openAPIV3Schema field. 237 If Crossplane cannot find a schema for the requested kind, the schema will be 238 empty (get_required_schema will return None). 239 """ 240 selector = fnv1.SchemaSelector( 241 api_version=api_version, 242 kind=kind, 243 ) 244 rsp.requirements.schemas[name].CopyFrom(selector)
Add a schema requirement to the response.
Arguments:
- rsp: The RunFunctionResponse to update.
- name: The name to use for this requirement.
- api_version: The API version of the resource kind, e.g. "example.org/v1".
- kind: The kind of resource, e.g. "MyResource".
This tells Crossplane to fetch the OpenAPI schema for the specified resource kind and include it in the next call to the function in req.required_schemas[name]. Use request.get_required_schema to retrieve it.
For CRDs, Crossplane returns the spec.versions[].schema.openAPIV3Schema field. If Crossplane cannot find a schema for the requested kind, the schema will be empty (get_required_schema will return None).