Skip to content

Commit

Permalink
Merge branch 'open-telemetry:main' into antonpirker/run-tests-in-pyth…
Browse files Browse the repository at this point in the history
…on-313
  • Loading branch information
antonpirker authored Aug 12, 2024
2 parents ee71a5c + 57d30b3 commit 12ef0b7
Show file tree
Hide file tree
Showing 44 changed files with 997 additions and 193 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update log export example to not use root logger ([#4090](https://github.com/open-telemetry/opentelemetry-python/pull/4090))
- sdk: Add OS resource detector
([#3992](https://github.com/open-telemetry/opentelemetry-python/pull/3992))
- sdk: Accept non URL-encoded headers in `OTEL_EXPORTER_OTLP_*HEADERS` to match other languages SDKs
([#4103](https://github.com/open-telemetry/opentelemetry-python/pull/4103))
- Update semantic conventions to version 1.27.0
([#4104](https://github.com/open-telemetry/opentelemetry-python/pull/4104))

## Version 1.26.0/0.47b0 (2024-07-25)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ def __init__(

self._headers = headers or environ.get(OTEL_EXPORTER_OTLP_HEADERS)
if isinstance(self._headers, str):
temp_headers = parse_env_headers(self._headers)
temp_headers = parse_env_headers(self._headers, liberal=True)
self._headers = tuple(temp_headers.items())
elif isinstance(self._headers, dict):
self._headers = tuple(self._headers.items())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ def __init__(
OTEL_EXPORTER_OTLP_LOGS_HEADERS,
environ.get(OTEL_EXPORTER_OTLP_HEADERS, ""),
)
self._headers = headers or parse_env_headers(headers_string)
self._headers = headers or parse_env_headers(
headers_string, liberal=True
)
self._timeout = timeout or int(
environ.get(
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ def __init__(
OTEL_EXPORTER_OTLP_METRICS_HEADERS,
environ.get(OTEL_EXPORTER_OTLP_HEADERS, ""),
)
self._headers = headers or parse_env_headers(headers_string)
self._headers = headers or parse_env_headers(
headers_string, liberal=True
)
self._timeout = timeout or int(
environ.get(
OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ def __init__(
OTEL_EXPORTER_OTLP_TRACES_HEADERS,
environ.get(OTEL_EXPORTER_OTLP_HEADERS, ""),
)
self._headers = headers or parse_env_headers(headers_string)
self._headers = headers or parse_env_headers(
headers_string, liberal=True
)
self._timeout = timeout or int(
environ.get(
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ def test_headers_parse_from_env(self):
(
"Header format invalid! Header values in environment "
"variables must be URL encoded per the OpenTelemetry "
"Protocol Exporter specification: missingValue"
"Protocol Exporter specification or a comma separated "
"list of name=value occurrences: missingValue"
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ def test_headers_parse_from_env(self):
(
"Header format invalid! Header values in environment "
"variables must be URL encoded per the OpenTelemetry "
"Protocol Exporter specification: missingValue"
"Protocol Exporter specification or a comma separated "
"list of name=value occurrences: missingValue"
),
)

Expand Down
56 changes: 45 additions & 11 deletions opentelemetry-api/src/opentelemetry/util/re.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,23 @@
_KEY_VALUE_FORMAT = rf"{_OWS}{_KEY_FORMAT}{_OWS}={_OWS}{_VALUE_FORMAT}{_OWS}"

_HEADER_PATTERN = compile(_KEY_VALUE_FORMAT)
_LIBERAL_HEADER_PATTERN = compile(
rf"{_OWS}{_KEY_FORMAT}{_OWS}={_OWS}[\w ]*{_OWS}"
)
_DELIMITER_PATTERN = compile(r"[ \t]*,[ \t]*")

_BAGGAGE_PROPERTY_FORMAT = rf"{_KEY_VALUE_FORMAT}|{_OWS}{_KEY_FORMAT}{_OWS}"

_INVALID_HEADER_ERROR_MESSAGE_STRICT_TEMPLATE = (
"Header format invalid! Header values in environment variables must be "
"URL encoded per the OpenTelemetry Protocol Exporter specification: %s"
)

_INVALID_HEADER_ERROR_MESSAGE_LIBERAL_TEMPLATE = (
"Header format invalid! Header values in environment variables must be "
"URL encoded per the OpenTelemetry Protocol Exporter specification or "
"a comma separated list of name=value occurrences: %s"
)

# pylint: disable=invalid-name

Expand All @@ -49,30 +62,51 @@ def parse_headers(s: str) -> Mapping[str, str]:
return parse_env_headers(s)


def parse_env_headers(s: str) -> Mapping[str, str]:
def parse_env_headers(s: str, liberal: bool = False) -> Mapping[str, str]:
"""
Parse ``s``, which is a ``str`` instance containing HTTP headers encoded
for use in ENV variables per the W3C Baggage HTTP header format at
https://www.w3.org/TR/baggage/#baggage-http-header-format, except that
additional semi-colon delimited metadata is not supported.
If ``liberal`` is True we try to parse ``s`` anyway to be more compatible
with other languages SDKs that accept non URL-encoded headers by default.
"""
headers: Dict[str, str] = {}
headers_list: List[str] = split(_DELIMITER_PATTERN, s)
for header in headers_list:
if not header: # empty string
continue
match = _HEADER_PATTERN.fullmatch(header.strip())
if not match:
header_match = _HEADER_PATTERN.fullmatch(header.strip())
if not header_match and not liberal:
_logger.warning(
"Header format invalid! Header values in environment variables must be "
"URL encoded per the OpenTelemetry Protocol Exporter specification: %s",
header,
_INVALID_HEADER_ERROR_MESSAGE_STRICT_TEMPLATE, header
)
continue
# value may contain any number of `=`
name, value = match.string.split("=", 1)
name = unquote(name).strip().lower()
value = unquote(value).strip()
headers[name] = value

if header_match:
match_string: str = header_match.string
# value may contain any number of `=`
name, value = match_string.split("=", 1)
name = unquote(name).strip().lower()
value = unquote(value).strip()
headers[name] = value
else:
# this is not url-encoded and does not match the spec but we decided to be
# liberal in what we accept to match other languages SDKs behaviour
liberal_header_match = _LIBERAL_HEADER_PATTERN.fullmatch(
header.strip()
)
if not liberal_header_match:
_logger.warning(
_INVALID_HEADER_ERROR_MESSAGE_LIBERAL_TEMPLATE, header
)
continue

liberal_match_string: str = liberal_header_match.string
# value may contain any number of `=`
name, value = liberal_match_string.split("=", 1)
name = name.strip().lower()
value = value.strip()
headers[name] = value

return headers
59 changes: 48 additions & 11 deletions opentelemetry-api/tests/util/test_re.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@


class TestParseHeaders(unittest.TestCase):
def test_parse_env_headers(self):
inp = [
@staticmethod
def _common_test_cases():
return [
# invalid header name
("=value", [], True),
("}key=value", [], True),
Expand Down Expand Up @@ -59,18 +60,54 @@ def test_parse_env_headers(self):
True,
),
]

def test_parse_env_headers(self):
inp = self._common_test_cases() + [
# invalid header value
("key=value othervalue", [], True),
]
for case_ in inp:
headers, expected, warn = case_
if warn:
with self.assertLogs(level="WARNING") as cm:
with self.subTest(headers=headers):
if warn:
with self.assertLogs(level="WARNING") as cm:
self.assertEqual(
parse_env_headers(headers), dict(expected)
)
self.assertTrue(
"Header format invalid! Header values in environment "
"variables must be URL encoded per the OpenTelemetry "
"Protocol Exporter specification:"
in cm.records[0].message,
)
else:
self.assertEqual(
parse_env_headers(headers), dict(expected)
)
self.assertTrue(
"Header format invalid! Header values in environment "
"variables must be URL encoded per the OpenTelemetry "
"Protocol Exporter specification:"
in cm.records[0].message,

def test_parse_env_headers_liberal(self):
inp = self._common_test_cases() + [
# valid header value
("key=value othervalue", [("key", "value othervalue")], False),
]
for case_ in inp:
headers, expected, warn = case_
with self.subTest(headers=headers):
if warn:
with self.assertLogs(level="WARNING") as cm:
self.assertEqual(
parse_env_headers(headers, liberal=True),
dict(expected),
)
self.assertTrue(
"Header format invalid! Header values in environment "
"variables must be URL encoded per the OpenTelemetry "
"Protocol Exporter specification or a comma separated "
"list of name=value occurrences:"
in cm.records[0].message,
)
else:
self.assertEqual(
parse_env_headers(headers, liberal=True),
dict(expected),
)
else:
self.assertEqual(parse_env_headers(headers), dict(expected))
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Final

ARTIFACT_ATTESTATION_FILENAME: Final = "artifact.attestation.filename"
"""
The provenance filename of the built attestation which directly relates to the build artifact filename. This filename SHOULD accompany the artifact at publish time. See the [SLSA Relationship](https://slsa.dev/spec/v1.0/distributing-provenance#relationship-between-artifacts-and-attestations) specification for more information.
"""

ARTIFACT_ATTESTATION_HASH: Final = "artifact.attestation.hash"
"""
The full [hash value (see glossary)](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf), of the built attestation. Some envelopes in the software attestation space also refer to this as the [digest](https://github.com/in-toto/attestation/blob/main/spec/README.md#in-toto-attestation-framework-spec).
"""

ARTIFACT_ATTESTATION_ID: Final = "artifact.attestation.id"
"""
The id of the build [software attestation](https://slsa.dev/attestation-model).
"""

ARTIFACT_FILENAME: Final = "artifact.filename"
"""
The human readable file name of the artifact, typically generated during build and release processes. Often includes the package name and version in the file name.
Note: This file name can also act as the [Package Name](https://slsa.dev/spec/v1.0/terminology#package-model)
in cases where the package ecosystem maps accordingly.
Additionally, the artifact [can be published](https://slsa.dev/spec/v1.0/terminology#software-supply-chain)
for others, but that is not a guarantee.
"""

ARTIFACT_HASH: Final = "artifact.hash"
"""
The full [hash value (see glossary)](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf), often found in checksum.txt on a release of the artifact and used to verify package integrity.
Note: The specific algorithm used to create the cryptographic hash value is
not defined. In situations where an artifact has multiple
cryptographic hashes, it is up to the implementer to choose which
hash value to set here; this should be the most secure hash algorithm
that is suitable for the situation and consistent with the
corresponding attestation. The implementer can then provide the other
hash values through an additional set of attribute extensions as they
deem necessary.
"""

ARTIFACT_PURL: Final = "artifact.purl"
"""
The [Package URL](https://github.com/package-url/purl-spec) of the [package artifact](https://slsa.dev/spec/v1.0/terminology#package-model) provides a standard way to identify and locate the packaged artifact.
"""

ARTIFACT_VERSION: Final = "artifact.version"
"""
The version of the artifact.
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Final

AZ_SERVICE_REQUEST_ID: Final = "az.service_request_id"
"""
The unique identifier of the service request. It's generated by the Azure service and returned with the response.
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from enum import Enum
from typing import Final

CICD_PIPELINE_NAME: Final = "cicd.pipeline.name"
"""
The human readable name of the pipeline within a CI/CD system.
"""

CICD_PIPELINE_RUN_ID: Final = "cicd.pipeline.run.id"
"""
The unique identifier of a pipeline run within a CI/CD system.
"""

CICD_PIPELINE_TASK_NAME: Final = "cicd.pipeline.task.name"
"""
The human readable name of a task within a pipeline. Task here most closely aligns with a [computing process](https://en.wikipedia.org/wiki/Pipeline_(computing)) in a pipeline. Other terms for tasks include commands, steps, and procedures.
"""

CICD_PIPELINE_TASK_RUN_ID: Final = "cicd.pipeline.task.run.id"
"""
The unique identifier of a task run within a pipeline.
"""

CICD_PIPELINE_TASK_RUN_URL_FULL: Final = "cicd.pipeline.task.run.url.full"
"""
The [URL](https://en.wikipedia.org/wiki/URL) of the pipeline run providing the complete address in order to locate and identify the pipeline run.
"""

CICD_PIPELINE_TASK_TYPE: Final = "cicd.pipeline.task.type"
"""
The type of the task within a pipeline.
"""


class CicdPipelineTaskTypeValues(Enum):
BUILD = "build"
"""build."""
TEST = "test"
"""test."""
DEPLOY = "deploy"
"""deploy."""
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
* **AWS Lambda:** The function [ARN](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html).
Take care not to use the "invoked ARN" directly but replace any
[alias suffix](https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html)
with the resolved function version, as the same runtime instance may be invokable with
with the resolved function version, as the same runtime instance may be invocable with
multiple different aliases.
* **GCP:** The [URI of the resource](https://cloud.google.com/iam/docs/full-resource-names)
* **Azure:** The [Fully Qualified Resource ID](https://docs.microsoft.com/rest/api/resources/resources/get-by-id) of the invoked function,
Expand Down
Loading

0 comments on commit 12ef0b7

Please sign in to comment.