From 1d8c77eabf17771aaaa659a46761b96e8d212188 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Tue, 10 Oct 2023 16:47:46 -0400 Subject: [PATCH 1/4] Governance docs per CE PR 1226 (#51) * Governance docs per CE PR 1226 Signed-off-by: Doug Davis * Update CONTRIBUTING.md Signed-off-by: John Laswell --------- Signed-off-by: Doug Davis Signed-off-by: John Laswell Co-authored-by: John Laswell --- CONTRIBUTING.md | 167 ++++++++++++++++++++++++++++++++++++++++++++++++ MAINTAINERS.md | 5 ++ README.md | 7 ++ RELEASING.md | 3 + 4 files changed, 182 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 MAINTAINERS.md create mode 100644 RELEASING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c6930f1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,167 @@ +# Contributing to CloudEvents' PHP SDK + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +We welcome contributions from the community! Please take some time to become +acquainted with the process before submitting a pull request. There are just +a few things to keep in mind. + +# Pull Requests + +Typically, a pull request should relate to an existing issue. If you have +found a bug, want to add an improvement, or suggest an API change, please +create an issue before proceeding with a pull request. For very minor changes +such as typos in the documentation this isn't really necessary. + +## Pull Request Guidelines + +Here you will find step by step guidance for creating, submitting and updating +a pull request in this repository. We hope it will help you have an easy time +managing your work and a positive, satisfying experience when contributing +your code. Thanks for getting involved! :rocket: + +* [Getting Started](#getting-started) +* [Branches](#branches) +* [Commit Messages](#commit-messages) +* [Staying current with main](#staying-current-with-main) +* [Submitting and Updating a Pull Request](#submitting-and-updating-a-pull-request) +* [Congratulations!](#congratulations) + +## Getting Started + +When creating a pull request, first fork this repository and clone it to your +local development environment. Then add this repository as the upstream. + +```console +git clone https://github.com/mygithuborg/sdk-php.git +cd sdk-php +git remote add upstream https://github.com/cloudevents/sdk-php.git +``` + +## Branches + +The first thing you'll need to do is create a branch for your work. +If you are submitting a pull request that fixes or relates to an existing +GitHub issue, you can use the issue number in your branch name to keep things +organized. + +```console +git fetch upstream +git reset --hard upstream/main +git checkout FETCH_HEAD +git checkout -b 48-fix-http-agent-error +``` + +## Commit Messages + +We prefer that contributors follow the +[Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/#summary). +The first line of your commit should be prefixed with a type, be a single +sentence with no period, and succinctly indicate what this commit changes. + +All commit message lines should be kept to fewer than 80 characters if possible. + +An example of a good commit message. + +```log +docs: remove 0.1, 0.2 spec support from README +``` + +### Signing your commits + +Each commit must be signed. Use the `--signoff` flag for your commits. + +```console +git commit --signoff +``` + +This will add a line to every git commit message: + + Signed-off-by: Joe Smith + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +The sign-off is a signature line at the end of your commit message. Your +signature certifies that you wrote the patch or otherwise have the right to pass +it on as open-source code. See [developercertificate.org](http://developercertificate.org/) +for the full text of the certification. + +Be sure to have your `user.name` and `user.email` set in your git config. +If your git config information is set properly then viewing the `git log` +information for your commit will look something like this: + +``` +Author: Joe Smith +Date: Thu Feb 2 11:41:15 2018 -0800 + + Update README + + Signed-off-by: Joe Smith +``` + +Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will +be rejected by the automated DCO check. + +## Staying Current with `main` + +As you are working on your branch, changes may happen on `main`. Before +submitting your pull request, be sure that your branch has been updated +with the latest commits. + +```console +git fetch upstream +git rebase upstream/main +``` + +This may cause conflicts if the files you are changing on your branch are +also changed on main. Error messages from `git` will indicate if conflicts +exist and what files need attention. Resolve the conflicts in each file, then +continue with the rebase with `git rebase --continue`. + + +If you've already pushed some changes to your `origin` fork, you'll +need to force push these changes. + +```console +git push -f origin 48-fix-http-agent-error +``` + +## Submitting and Updating Your Pull Request + +Before submitting a pull request, you should make sure that all of the tests +successfully pass. + +Once you have sent your pull request, `main` may continue to evolve +before your pull request has landed. If there are any commits on `main` +that conflict with your changes, you may need to update your branch with +these changes before the pull request can land. Resolve conflicts the same +way as before. + +```console +git fetch upstream +git rebase upstream/main +# fix any potential conflicts +git push -f origin 48-fix-http-agent-error +``` + +This will cause the pull request to be updated with your changes, and +CI will rerun. + +A maintainer may ask you to make changes to your pull request. Sometimes these +changes are minor and shouldn't appear in the commit log. For example, you may +have a typo in one of your code comments that should be fixed before merge. +You can prevent this from adding noise to the commit log with an interactive +rebase. See the [git documentation](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) +for details. + +```console +git commit -m "fixup: fix typo" +git rebase -i upstream/main # follow git instructions +``` + +Once you have rebased your commits, you can force push to your fork as before. + +## Congratulations! + +Congratulations! You've done it! We really appreciate the time and energy +you've given to the project. Thank you. diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..b57fc6d --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,5 @@ +# Maintainers + +Current active maintainers of this SDK: + +- [John Laswell](https://github.com/jlaswell) diff --git a/README.md b/README.md index 35fae23..4428904 100644 --- a/README.md +++ b/README.md @@ -117,3 +117,10 @@ how SDK projects are for how PR reviews and approval, and our [Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md) information. + +## Additional SDK Resources + +- [List of current active maintainers](MAINTAINERS.md) +- [How to contribute to the project](CONTRIBUTING.md) +- [SDK's License](LICENSE) +- [SDK's Release process](RELEASING.md) diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..c20795f --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,3 @@ +To create a new release: +- Create a new Github release via the Github UI, making sure to prefix it + with `v` From c788eeb371ecb0b296c30e4d131507067ec3b7a0 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Thu, 12 Oct 2023 17:58:26 +0100 Subject: [PATCH 2/4] PHP 8.3 support (#52) * PHP 8.3 support Signed-off-by: Graham Campbell * Reverted bad phpunit config file change Signed-off-by: Graham Campbell --------- Signed-off-by: Graham Campbell --- .github/workflows/static.yml | 10 +++++----- .github/workflows/tests.yml | 6 +++--- composer.json | 6 ++++-- hack/8.3.Dockerfile | 22 ++++++++++++++++++++++ phpunit.xml.dist | 7 +++---- 5 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 hack/8.3.Dockerfile diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index cb5e2d0..4f22379 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -8,11 +8,11 @@ on: jobs: codesniffer: name: PHP CodeSniffer - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -24,7 +24,7 @@ jobs: update: true - name: Install Dependencies - uses: nick-invision/retry@v1 + uses: nick-invision/retry@v2 with: timeout_minutes: 5 max_attempts: 5 @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -51,7 +51,7 @@ jobs: update: true - name: Install Dependencies - uses: nick-invision/retry@v1 + uses: nick-invision/retry@v2 with: timeout_minutes: 5 max_attempts: 5 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 38b1a4e..5e2e15f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,11 +12,11 @@ jobs: strategy: matrix: - php: ['7.4', '8.0', '8.1', '8.2'] + php: ['7.4', '8.0', '8.1', '8.2', '8.3'] steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -31,7 +31,7 @@ jobs: run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Install Dependencies - uses: nick-invision/retry@v1 + uses: nick-invision/retry@v2 with: timeout_minutes: 5 max_attempts: 5 diff --git a/composer.json b/composer.json index dbcd84b..a417955 100644 --- a/composer.json +++ b/composer.json @@ -48,13 +48,15 @@ "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:7.4-tests -f hack/7.4.Dockerfile hack", "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.0-tests -f hack/8.0.Dockerfile hack", "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.1-tests -f hack/8.1.Dockerfile hack", - "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.2-tests -f hack/8.1.Dockerfile hack" + "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.2-tests -f hack/8.1.Dockerfile hack", + "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.3-tests -f hack/8.1.Dockerfile hack" ], "tests-docker": [ "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:7.4-tests --coverage-html=coverage", "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:8.0-tests", "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:8.1-tests", - "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:8.2-tests" + "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:8.2-tests", + "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:8.3-tests" ] }, "scripts-descriptions": { diff --git a/hack/8.3.Dockerfile b/hack/8.3.Dockerfile new file mode 100644 index 0000000..f32111f --- /dev/null +++ b/hack/8.3.Dockerfile @@ -0,0 +1,22 @@ +FROM php:8.3-rc-alpine + +LABEL org.opencontainers.image.url="https://github.com/cloudevents/sdk-php/tree/main/hack/8.3.Dockerfile" \ + org.opencontainers.image.documentation="https://github.com/cloudevents/sdk-php/tree/main/hack/README.md" \ + org.opencontainers.image.source="https://github.com/cloudevents/sdk-php" \ + org.opencontainers.image.vendor="CloudEvent" \ + org.opencontainers.image.title="PHP 8.3" \ + org.opencontainers.image.description="PHP 8.3 test environment for cloudevents/sdk-php" + +COPY --chown=www-data:www-data install-composer /usr/local/bin/install-composer +RUN chmod +x /usr/local/bin/install-composer \ + && /usr/local/bin/install-composer \ + && rm /usr/local/bin/install-composer + +RUN apk update \ + && apk --no-cache upgrade \ + && apk add --no-cache bash ca-certificates git libzip-dev \ + && rm -rf /var/www/html /tmp/pear \ + && chown -R www-data:www-data /var/www + +WORKDIR /var/www +ENTRYPOINT ["/var/www/vendor/bin/phpunit"] diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0f08581..39df3df 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,14 +1,13 @@ - + tests/Unit - - + src - + From 7e18f334d98275bf85b6f01b1aacbf272d6d1214 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Mon, 16 Oct 2023 09:17:20 -0400 Subject: [PATCH 3/4] add link to our security mailing list (#53) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4428904..e7a0cdb 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,10 @@ for how PR reviews and approval, and our [Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md) information. +If there is a security concern with one of the CloudEvents specifications, or +with one of the project's SDKs, please send an email to +[cncf-cloudevents-security@lists.cncf.io](mailto:cncf-cloudevents-security@lists.cncf.io). + ## Additional SDK Resources - [List of current active maintainers](MAINTAINERS.md) From 45c5e1b5c4734bbba2e23ae41d06e7902d35087b Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 18 Dec 2023 02:56:02 +0000 Subject: [PATCH 4/4] Enable configuration of subsecond precision of timestamps (#54) Signed-off-by: Graham Campbell --- composer.json | 6 ++-- src/Serializers/Normalizers/V1/Normalizer.php | 15 +++++++- src/Utilities/AttributeConverter.php | 6 ++-- src/Utilities/TimeFormatter.php | 28 +++++++++++++-- .../Normalizers/V1/NormalizerTest.php | 36 +++++++++++++++++++ tests/Unit/Utilities/TimeFormatterTest.php | 24 ++++++++++--- 6 files changed, 102 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index a417955..cba6b96 100644 --- a/composer.json +++ b/composer.json @@ -48,8 +48,8 @@ "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:7.4-tests -f hack/7.4.Dockerfile hack", "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.0-tests -f hack/8.0.Dockerfile hack", "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.1-tests -f hack/8.1.Dockerfile hack", - "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.2-tests -f hack/8.1.Dockerfile hack", - "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.3-tests -f hack/8.1.Dockerfile hack" + "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.2-tests -f hack/8.2.Dockerfile hack", + "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.3-tests -f hack/8.3.Dockerfile hack" ], "tests-docker": [ "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:7.4-tests --coverage-html=coverage", @@ -76,7 +76,7 @@ }, "extra": { "branch-alias": { - "dev-main": "1.0-dev" + "dev-main": "1.1-dev" } }, "minimum-stability": "dev", diff --git a/src/Serializers/Normalizers/V1/Normalizer.php b/src/Serializers/Normalizers/V1/Normalizer.php index d5116bf..3ba9cff 100644 --- a/src/Serializers/Normalizers/V1/Normalizer.php +++ b/src/Serializers/Normalizers/V1/Normalizer.php @@ -10,13 +10,26 @@ final class Normalizer implements NormalizerInterface { + /** + * @var array{subsecondPrecision?: int<0, 6>} + */ + private array $configuration; + + /** + * @param array{subsecondPrecision?: int<0, 6>} $configuration + */ + public function __construct(array $configuration = []) + { + $this->configuration = $configuration; + } + /** * @return array */ public function normalize(CloudEventInterface $cloudEvent, bool $rawData): array { return array_merge( - AttributeConverter::toArray($cloudEvent), + AttributeConverter::toArray($cloudEvent, $this->configuration), DataFormatter::encode($cloudEvent->getData(), $rawData) ); } diff --git a/src/Utilities/AttributeConverter.php b/src/Utilities/AttributeConverter.php index 880c591..512ccdb 100644 --- a/src/Utilities/AttributeConverter.php +++ b/src/Utilities/AttributeConverter.php @@ -13,9 +13,11 @@ final class AttributeConverter { /** + * @param array{subsecondPrecision?: int<0, 6>} $configuration + * * @return array */ - public static function toArray(CloudEventInterface $cloudEvent): array + public static function toArray(CloudEventInterface $cloudEvent, array $configuration): array { /** @var array */ $attributes = array_filter([ @@ -26,7 +28,7 @@ public static function toArray(CloudEventInterface $cloudEvent): array 'datacontenttype' => $cloudEvent->getDataContentType(), 'dataschema' => $cloudEvent->getDataSchema(), 'subject' => $cloudEvent->getSubject(), - 'time' => TimeFormatter::encode($cloudEvent->getTime()), + 'time' => TimeFormatter::encode($cloudEvent->getTime(), $configuration['subsecondPrecision'] ?? 0), ], fn ($attr) => $attr !== null); return array_merge($attributes, $cloudEvent->getExtensions()); diff --git a/src/Utilities/TimeFormatter.php b/src/Utilities/TimeFormatter.php index 6d6bbe4..b27f9c7 100644 --- a/src/Utilities/TimeFormatter.php +++ b/src/Utilities/TimeFormatter.php @@ -13,19 +13,41 @@ */ final class TimeFormatter { - private const TIME_FORMAT = 'Y-m-d\TH:i:s\Z'; + private const TIME_FORMAT = 'Y-m-d\TH:i:s'; + private const TIME_FORMAT_EXTENDED = 'Y-m-d\TH:i:s.u'; private const TIME_ZONE = 'UTC'; private const RFC3339_FORMAT = 'Y-m-d\TH:i:sP'; private const RFC3339_EXTENDED_FORMAT = 'Y-m-d\TH:i:s.uP'; - public static function encode(?DateTimeImmutable $time): ?string + /** + * @param int<0, 6> $subsecondPrecision + */ + public static function encode(?DateTimeImmutable $time, int $subsecondPrecision): ?string { if ($time === null) { return null; } - return $time->setTimezone(new DateTimeZone(self::TIME_ZONE))->format(self::TIME_FORMAT); + return sprintf('%sZ', self::encodeWithoutTimezone($time, $subsecondPrecision)); + } + + /** + * @param int<0, 6> $subsecondPrecision + */ + private static function encodeWithoutTimezone(DateTimeImmutable $time, int $subsecondPrecision): string + { + $utcTime = $time->setTimezone(new DateTimeZone(self::TIME_ZONE)); + + if ($subsecondPrecision <= 0) { + return $utcTime->format(self::TIME_FORMAT); + } + + if ($subsecondPrecision >= 6) { + return $utcTime->format(self::TIME_FORMAT_EXTENDED); + } + + return substr($utcTime->format(self::TIME_FORMAT_EXTENDED), 0, $subsecondPrecision - 6); } public static function decode(?string $time): ?DateTimeImmutable diff --git a/tests/Unit/Serializers/Normalizers/V1/NormalizerTest.php b/tests/Unit/Serializers/Normalizers/V1/NormalizerTest.php index 19487b0..e91f990 100644 --- a/tests/Unit/Serializers/Normalizers/V1/NormalizerTest.php +++ b/tests/Unit/Serializers/Normalizers/V1/NormalizerTest.php @@ -80,4 +80,40 @@ public function testNormalizerWithUnsetAttributes(): void $formatter->normalize($event, false) ); } + + public function testNormalizerWithSubsecondPrecisionConfiguration(): void + { + /** @var CloudEventInterface|Stub $event */ + $event = $this->createStub(CloudEventInterface::class); + $event->method('getSpecVersion')->willReturn('1.0'); + $event->method('getId')->willReturn('1234-1234-1234'); + $event->method('getSource')->willReturn('/var/data'); + $event->method('getType')->willReturn('com.example.someevent'); + $event->method('getDataContentType')->willReturn('application/json'); + $event->method('getDataSchema')->willReturn('com.example/schema'); + $event->method('getSubject')->willReturn('larger-context'); + $event->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00.123456Z')); + $event->method('getData')->willReturn(['key' => 'value']); + $event->method('getExtensions')->willReturn(['comacme' => 'foo']); + + $formatter = new Normalizer(['subsecondPrecision' => 3]); + + self::assertSame( + [ + 'specversion' => '1.0', + 'id' => '1234-1234-1234', + 'source' => '/var/data', + 'type' => 'com.example.someevent', + 'datacontenttype' => 'application/json', + 'dataschema' => 'com.example/schema', + 'subject' => 'larger-context', + 'time' => '2018-04-05T17:31:00.123Z', + 'comacme' => 'foo', + 'data' => [ + 'key' => 'value', + ], + ], + $formatter->normalize($event, false) + ); + } } diff --git a/tests/Unit/Utilities/TimeFormatterTest.php b/tests/Unit/Utilities/TimeFormatterTest.php index 06d8d44..00426e2 100644 --- a/tests/Unit/Utilities/TimeFormatterTest.php +++ b/tests/Unit/Utilities/TimeFormatterTest.php @@ -11,11 +11,27 @@ class TimeFormatterTest extends TestCase { - public function testEncode(): void + public static function providesValidEncodeCases(): array + { + return [ + ['2018-04-05T17:31:00Z', '2018-04-05T17:31:00.123456Z', 0], + ['2018-04-05T17:31:00.1Z', '2018-04-05T17:31:00.123456Z', 1], + ['2018-04-05T17:31:00.12Z', '2018-04-05T17:31:00.123456Z', 2], + ['2018-04-05T17:31:00.123Z', '2018-04-05T17:31:00.123456Z', 3], + ['2018-04-05T17:31:00.1234Z', '2018-04-05T17:31:00.123456Z', 4], + ['2018-04-05T17:31:00.12345Z', '2018-04-05T17:31:00.123456Z', 5], + ['2018-04-05T17:31:00.123456Z', '2018-04-05T17:31:00.123456Z', 6], + ]; + } + + /** + * @dataProvider providesValidEncodeCases + */ + public function testEncode(string $expected, string $input, int $subsecondPrecision): void { self::assertEquals( - '2018-04-05T17:31:00Z', - TimeFormatter::encode(new DateTimeImmutable('2018-04-05T17:31:00Z')) + $expected, + TimeFormatter::encode(new DateTimeImmutable($input), $subsecondPrecision) ); } @@ -83,7 +99,7 @@ public function testEncodeEmpty(): void { self::assertEquals( null, - TimeFormatter::encode(null) + TimeFormatter::encode(null, 0) ); }