diff --git a/scripts/Makefile b/scripts/Makefile index bc09616..73b3649 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -12,3 +12,9 @@ docker-test: build-python-bats docker-acceptance: build-python-bats docker run --entrypoint "/usr/bin/bats" -t python-bats /code/acceptance.bats + +get-crds: +# get emissary mapping v2 CRD + curl -Lnk https://app.getambassador.io/yaml/emissary/3.8.0/emissary-crds.yaml | yq -o json |\ + jq -s '.[] | select(.metadata.name=="mappings.getambassador.io") | del(.spec.versions[] | select( .name != "v2"))' |\ + yq -p json -o yaml > fixtures/emissary-mapping-crd.yaml \ No newline at end of file diff --git a/scripts/fixtures/emissary-mapping-crd.yaml b/scripts/fixtures/emissary-mapping-crd.yaml new file mode 100644 index 0000000..bffbca7 --- /dev/null +++ b/scripts/fixtures/emissary-mapping-crd.yaml @@ -0,0 +1,455 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + labels: + app.kubernetes.io/instance: emissary-apiext + app.kubernetes.io/managed-by: kubectl_apply_-f_emissary-apiext.yaml + app.kubernetes.io/name: emissary-apiext + app.kubernetes.io/part-of: emissary-apiext + name: mappings.getambassador.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: emissary-apiext + namespace: emissary-system + conversionReviewVersions: + - v1 + group: getambassador.io + names: + categories: + - ambassador-crds + kind: Mapping + listKind: MappingList + plural: mappings + singular: mapping + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.host + name: Source Host + type: string + - jsonPath: .spec.prefix + name: Source Prefix + type: string + - jsonPath: .spec.service + name: Dest Service + type: string + - jsonPath: .status.state + name: State + type: string + - jsonPath: .status.reason + name: Reason + type: string + name: v2 + schema: + openAPIV3Schema: + description: Mapping is the Schema for the mappings API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation 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 + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: MappingSpec defines the desired state of Mapping + properties: + add_linkerd_headers: + type: boolean + add_request_headers: + type: object + x-kubernetes-preserve-unknown-fields: true + add_response_headers: + type: object + x-kubernetes-preserve-unknown-fields: true + allow_upgrade: + description: "A case-insensitive list of the non-HTTP protocols to allow \"upgrading\" to from HTTP via the \"Connection: upgrade\" mechanism[1]. After the upgrade, Ambassador does not interpret the traffic, and behaves similarly to how it does for TCPMappings. \n [1]: https://tools.ietf.org/html/rfc7230#section-6.7 \n For example, if your upstream service supports WebSockets, you would write \n allow_upgrade: - websocket \n Or if your upstream service supports upgrading from HTTP to SPDY (as the Kubernetes apiserver does for `kubectl exec` functionality), you would write \n allow_upgrade: - spdy/3.1" + items: + type: string + type: array + auth_context_extensions: + additionalProperties: + type: string + type: object + auto_host_rewrite: + type: boolean + bypass_auth: + type: boolean + bypass_error_response_overrides: + description: If true, bypasses any `error_response_overrides` set on the Ambassador module. + type: boolean + case_sensitive: + type: boolean + circuit_breakers: + items: + properties: + max_connections: + type: integer + max_pending_requests: + type: integer + max_requests: + type: integer + max_retries: + type: integer + priority: + enum: + - default + - high + type: string + type: object + type: array + cluster_idle_timeout_ms: + type: integer + cluster_max_connection_lifetime_ms: + type: integer + cluster_tag: + type: string + connect_timeout_ms: + type: integer + cors: + properties: + credentials: + type: boolean + max_age: + type: string + type: object + x-kubernetes-preserve-unknown-fields: true + dns_type: + type: string + docs: + description: DocsInfo provides some extra information about the docs for the Mapping (used by the Dev Portal) + properties: + display_name: + type: string + ignored: + type: boolean + path: + type: string + timeout_ms: + type: integer + url: + type: string + type: object + enable_ipv4: + type: boolean + enable_ipv6: + type: boolean + envoy_override: + type: object + x-kubernetes-preserve-unknown-fields: true + error_response_overrides: + description: Error response overrides for this Mapping. Replaces all of the `error_response_overrides` set on the Ambassador module, if any. + items: + description: A response rewrite for an HTTP error response + properties: + body: + description: The new response body + properties: + content_type: + description: The content type to set on the error response body when using text_format or text_format_source. Defaults to 'text/plain'. + type: string + json_format: + additionalProperties: + type: string + description: 'A JSON response with content-type: application/json. The values can contain format text like in text_format.' + type: object + text_format: + description: A format string representing a text response body. Content-Type can be set using the `content_type` field below. + type: string + text_format_source: + description: A format string sourced from a file on the Ambassador container. Useful for larger response bodies that should not be placed inline in configuration. + properties: + filename: + description: The name of a file on the Ambassador pod that contains a format text string. + type: string + type: object + type: object + on_status_code: + description: The status code to match on -- not a pointer because it's required. + maximum: 599 + minimum: 400 + type: integer + required: + - body + - on_status_code + type: object + minItems: 1 + type: array + grpc: + type: boolean + headers: + type: object + x-kubernetes-preserve-unknown-fields: true + host: + type: string + host_redirect: + type: boolean + host_regex: + type: boolean + host_rewrite: + type: string + idle_timeout_ms: + type: integer + keepalive: + properties: + idle_time: + type: integer + interval: + type: integer + probes: + type: integer + type: object + labels: + additionalProperties: + description: A MappingLabelGroupsArray is an array of MappingLabelGroups. I know, complex. + items: + description: 'A MappingLabelGroup is a single element of a MappingLabelGroupsArray: a second map, where the key is a human-readable name that identifies the group.' + maxProperties: 1 + minProperties: 1 + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + description: A DomainMap is the overall Mapping.spec.Labels type. It maps domains (kind of like namespaces for Mapping labels) to arrays of label groups. + type: object + load_balancer: + properties: + cookie: + properties: + name: + type: string + path: + type: string + ttl: + type: string + required: + - name + type: object + header: + type: string + policy: + enum: + - round_robin + - ring_hash + - maglev + - least_request + type: string + source_ip: + type: boolean + required: + - policy + type: object + method: + type: string + method_regex: + type: boolean + modules: + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + outlier_detection: + type: string + path_redirect: + description: Path replacement to use when generating an HTTP redirect. Used with `host_redirect`. + type: string + precedence: + type: integer + prefix: + type: string + prefix_exact: + type: boolean + prefix_redirect: + description: Prefix rewrite to use when generating an HTTP redirect. Used with `host_redirect`. + type: string + prefix_regex: + type: boolean + priority: + type: string + query_parameters: + type: object + x-kubernetes-preserve-unknown-fields: true + redirect_response_code: + description: The response code to use when generating an HTTP redirect. Defaults to 301. Used with `host_redirect`. + enum: + - 301 + - 302 + - 303 + - 307 + - 308 + type: integer + regex_headers: + additionalProperties: + type: string + type: object + regex_query_parameters: + additionalProperties: + type: string + type: object + regex_redirect: + description: Prefix regex rewrite to use when generating an HTTP redirect. Used with `host_redirect`. + properties: + pattern: + type: string + substitution: + type: string + type: object + regex_rewrite: + properties: + pattern: + type: string + substitution: + type: string + type: object + resolver: + type: string + respect_dns_ttl: + type: boolean + retry_policy: + properties: + num_retries: + type: integer + per_try_timeout: + type: string + retry_on: + enum: + - 5xx + - gateway-error + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + type: string + type: object + rewrite: + type: string + service: + type: string + shadow: + type: boolean + timeout_ms: + description: The timeout for requests that use this Mapping. Overrides `cluster_request_timeout_ms` set on the Ambassador Module, if it exists. + type: integer + use_websocket: + description: 'use_websocket is deprecated, and is equivlaent to setting `allow_upgrade: ["websocket"]`' + type: boolean + v3StatsName: + type: string + v3health_checks: + items: + description: HealthCheck specifies settings for performing active health checking on upstreams + properties: + health_check: + description: Configuration for where the healthcheck request should be made to + maxProperties: 1 + minProperties: 1 + properties: + grpc: + description: HealthCheck for gRPC upstreams. Only one of grpc_health_check or http_health_check may be specified + properties: + authority: + description: The value of the :authority header in the gRPC health check request. If left empty the upstream name will be used. + type: string + upstream_name: + description: The upstream name parameter which will be sent to gRPC service in the health check message + type: string + required: + - upstream_name + type: object + http: + description: HealthCheck for HTTP upstreams. Only one of http_health_check or grpc_health_check may be specified + properties: + add_request_headers: + additionalProperties: + properties: + append: + type: boolean + v2Representation: + enum: + - "" + - string + - "null" + type: string + value: + type: string + type: object + type: object + expected_statuses: + items: + description: A range of response statuses from Start to End inclusive + properties: + max: + description: End of the statuses to include. Must be between 100 and 599 (inclusive) + maximum: 599 + minimum: 100 + type: integer + min: + description: Start of the statuses to include. Must be between 100 and 599 (inclusive) + maximum: 599 + minimum: 100 + type: integer + required: + - max + - min + type: object + type: array + hostname: + type: string + path: + type: string + remove_request_headers: + items: + type: string + type: array + required: + - path + type: object + type: object + healthy_threshold: + description: Number of expected responses for the upstream to be considered healthy. Defaults to 1. + type: integer + interval: + description: Interval between health checks. Defaults to every 5 seconds. + type: string + timeout: + description: Timeout for connecting to the health checking endpoint. Defaults to 3 seconds. + type: string + unhealthy_threshold: + description: Number of non-expected responses for the upstream to be considered unhealthy. A single 503 will mark the upstream as unhealthy regardless of the threshold. Defaults to 2. + type: integer + required: + - health_check + type: object + minItems: 1 + type: array + weight: + type: integer + required: + - prefix + - service + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: MappingStatus defines the observed state of Mapping + properties: + reason: + type: string + state: + enum: + - "" + - Inactive + - Running + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/scripts/fixtures/mapping_v2-expected.json b/scripts/fixtures/mapping_v2-expected.json new file mode 100644 index 0000000..623e76b --- /dev/null +++ b/scripts/fixtures/mapping_v2-expected.json @@ -0,0 +1,590 @@ +{ + "description": "Mapping is the Schema for the mappings API", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation 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" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MappingSpec defines the desired state of Mapping", + "properties": { + "add_linkerd_headers": { + "type": "boolean" + }, + "add_request_headers": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "add_response_headers": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "allow_upgrade": { + "description": "A case-insensitive list of the non-HTTP protocols to allow \"upgrading\" to from HTTP via the \"Connection: upgrade\" mechanism[1]. After the upgrade, Ambassador does not interpret the traffic, and behaves similarly to how it does for TCPMappings. \n [1]: https://tools.ietf.org/html/rfc7230#section-6.7 \n For example, if your upstream service supports WebSockets, you would write \n allow_upgrade: - websocket \n Or if your upstream service supports upgrading from HTTP to SPDY (as the Kubernetes apiserver does for `kubectl exec` functionality), you would write \n allow_upgrade: - spdy/3.1", + "items": { + "type": "string" + }, + "type": "array" + }, + "auth_context_extensions": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "auto_host_rewrite": { + "type": "boolean" + }, + "bypass_auth": { + "type": "boolean" + }, + "bypass_error_response_overrides": { + "description": "If true, bypasses any `error_response_overrides` set on the Ambassador module.", + "type": "boolean" + }, + "case_sensitive": { + "type": "boolean" + }, + "circuit_breakers": { + "items": { + "properties": { + "max_connections": { + "type": "integer" + }, + "max_pending_requests": { + "type": "integer" + }, + "max_requests": { + "type": "integer" + }, + "max_retries": { + "type": "integer" + }, + "priority": { + "enum": [ + "default", + "high" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "cluster_idle_timeout_ms": { + "type": "integer" + }, + "cluster_max_connection_lifetime_ms": { + "type": "integer" + }, + "cluster_tag": { + "type": "string" + }, + "connect_timeout_ms": { + "type": "integer" + }, + "cors": { + "properties": { + "credentials": { + "type": "boolean" + }, + "max_age": { + "type": "string" + } + }, + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "dns_type": { + "type": "string" + }, + "docs": { + "description": "DocsInfo provides some extra information about the docs for the Mapping (used by the Dev Portal)", + "properties": { + "display_name": { + "type": "string" + }, + "ignored": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "timeout_ms": { + "type": "integer" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "enable_ipv4": { + "type": "boolean" + }, + "enable_ipv6": { + "type": "boolean" + }, + "envoy_override": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "error_response_overrides": { + "description": "Error response overrides for this Mapping. Replaces all of the `error_response_overrides` set on the Ambassador module, if any.", + "items": { + "description": "A response rewrite for an HTTP error response", + "properties": { + "body": { + "description": "The new response body", + "properties": { + "content_type": { + "description": "The content type to set on the error response body when using text_format or text_format_source. Defaults to 'text/plain'.", + "type": "string" + }, + "json_format": { + "additionalProperties": { + "type": "string" + }, + "description": "A JSON response with content-type: application/json. The values can contain format text like in text_format.", + "type": "object" + }, + "text_format": { + "description": "A format string representing a text response body. Content-Type can be set using the `content_type` field below.", + "type": "string" + }, + "text_format_source": { + "description": "A format string sourced from a file on the Ambassador container. Useful for larger response bodies that should not be placed inline in configuration.", + "properties": { + "filename": { + "description": "The name of a file on the Ambassador pod that contains a format text string.", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "on_status_code": { + "description": "The status code to match on -- not a pointer because it's required.", + "maximum": 599, + "minimum": 400, + "type": "integer" + } + }, + "required": [ + "body", + "on_status_code" + ], + "type": "object", + "additionalProperties": false + }, + "minItems": 1, + "type": "array" + }, + "grpc": { + "type": "boolean" + }, + "headers": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "host": { + "type": "string" + }, + "host_redirect": { + "type": "boolean" + }, + "host_regex": { + "type": "boolean" + }, + "host_rewrite": { + "type": "string" + }, + "idle_timeout_ms": { + "type": "integer" + }, + "keepalive": { + "properties": { + "idle_time": { + "type": "integer" + }, + "interval": { + "type": "integer" + }, + "probes": { + "type": "integer" + } + }, + "type": "object", + "additionalProperties": false + }, + "labels": { + "additionalProperties": { + "description": "A MappingLabelGroupsArray is an array of MappingLabelGroups. I know, complex.", + "items": { + "description": "A MappingLabelGroup is a single element of a MappingLabelGroupsArray: a second map, where the key is a human-readable name that identifies the group.", + "maxProperties": 1, + "minProperties": 1, + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array" + }, + "description": "A DomainMap is the overall Mapping.spec.Labels type. It maps domains (kind of like namespaces for Mapping labels) to arrays of label groups.", + "type": "object" + }, + "load_balancer": { + "properties": { + "cookie": { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "ttl": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "header": { + "type": "string" + }, + "policy": { + "enum": [ + "round_robin", + "ring_hash", + "maglev", + "least_request" + ], + "type": "string" + }, + "source_ip": { + "type": "boolean" + } + }, + "required": [ + "policy" + ], + "type": "object", + "additionalProperties": false + }, + "method": { + "type": "string" + }, + "method_regex": { + "type": "boolean" + }, + "modules": { + "items": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array" + }, + "outlier_detection": { + "type": "string" + }, + "path_redirect": { + "description": "Path replacement to use when generating an HTTP redirect. Used with `host_redirect`.", + "type": "string" + }, + "precedence": { + "type": "integer" + }, + "prefix": { + "type": "string" + }, + "prefix_exact": { + "type": "boolean" + }, + "prefix_redirect": { + "description": "Prefix rewrite to use when generating an HTTP redirect. Used with `host_redirect`.", + "type": "string" + }, + "prefix_regex": { + "type": "boolean" + }, + "priority": { + "type": "string" + }, + "query_parameters": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "redirect_response_code": { + "description": "The response code to use when generating an HTTP redirect. Defaults to 301. Used with `host_redirect`.", + "enum": [ + 301, + 302, + 303, + 307, + 308 + ], + "type": "integer" + }, + "regex_headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "regex_query_parameters": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "regex_redirect": { + "description": "Prefix regex rewrite to use when generating an HTTP redirect. Used with `host_redirect`.", + "properties": { + "pattern": { + "type": "string" + }, + "substitution": { + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "regex_rewrite": { + "properties": { + "pattern": { + "type": "string" + }, + "substitution": { + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "resolver": { + "type": "string" + }, + "respect_dns_ttl": { + "type": "boolean" + }, + "retry_policy": { + "properties": { + "num_retries": { + "type": "integer" + }, + "per_try_timeout": { + "type": "string" + }, + "retry_on": { + "enum": [ + "5xx", + "gateway-error", + "connect-failure", + "retriable-4xx", + "refused-stream", + "retriable-status-codes" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "rewrite": { + "type": "string" + }, + "service": { + "type": "string" + }, + "shadow": { + "type": "boolean" + }, + "timeout_ms": { + "description": "The timeout for requests that use this Mapping. Overrides `cluster_request_timeout_ms` set on the Ambassador Module, if it exists.", + "type": "integer" + }, + "use_websocket": { + "description": "use_websocket is deprecated, and is equivlaent to setting `allow_upgrade: [\"websocket\"]`", + "type": "boolean" + }, + "v3StatsName": { + "type": "string" + }, + "v3health_checks": { + "items": { + "description": "HealthCheck specifies settings for performing active health checking on upstreams", + "properties": { + "health_check": { + "description": "Configuration for where the healthcheck request should be made to", + "maxProperties": 1, + "minProperties": 1, + "properties": { + "grpc": { + "description": "HealthCheck for gRPC upstreams. Only one of grpc_health_check or http_health_check may be specified", + "properties": { + "authority": { + "description": "The value of the :authority header in the gRPC health check request. If left empty the upstream name will be used.", + "type": "string" + }, + "upstream_name": { + "description": "The upstream name parameter which will be sent to gRPC service in the health check message", + "type": "string" + } + }, + "required": [ + "upstream_name" + ], + "type": "object", + "additionalProperties": false + }, + "http": { + "description": "HealthCheck for HTTP upstreams. Only one of http_health_check or grpc_health_check may be specified", + "properties": { + "add_request_headers": { + "additionalProperties": { + "properties": { + "append": { + "type": "boolean" + }, + "v2Representation": { + "enum": [ + "", + "string", + "null" + ], + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "type": "object" + }, + "expected_statuses": { + "items": { + "description": "A range of response statuses from Start to End inclusive", + "properties": { + "max": { + "description": "End of the statuses to include. Must be between 100 and 599 (inclusive)", + "maximum": 599, + "minimum": 100, + "type": "integer" + }, + "min": { + "description": "Start of the statuses to include. Must be between 100 and 599 (inclusive)", + "maximum": 599, + "minimum": 100, + "type": "integer" + } + }, + "required": [ + "max", + "min" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "hostname": { + "type": "string" + }, + "path": { + "type": "string" + }, + "remove_request_headers": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "path" + ], + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "healthy_threshold": { + "description": "Number of expected responses for the upstream to be considered healthy. Defaults to 1.", + "type": "integer" + }, + "interval": { + "description": "Interval between health checks. Defaults to every 5 seconds.", + "type": "string" + }, + "timeout": { + "description": "Timeout for connecting to the health checking endpoint. Defaults to 3 seconds.", + "type": "string" + }, + "unhealthy_threshold": { + "description": "Number of non-expected responses for the upstream to be considered unhealthy. A single 503 will mark the upstream as unhealthy regardless of the threshold. Defaults to 2.", + "type": "integer" + } + }, + "required": [ + "health_check" + ], + "type": "object", + "additionalProperties": false + }, + "minItems": 1, + "type": "array" + }, + "weight": { + "type": "integer" + } + }, + "required": [ + "prefix", + "service" + ], + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "status": { + "description": "MappingStatus defines the observed state of Mapping", + "properties": { + "reason": { + "type": "string" + }, + "state": { + "enum": [ + "", + "Inactive", + "Running" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +}