diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..410d4a1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +/.github export-ignore +/tests export-ignore +/.gitignore export-ignore +/Makefile export-ignore +/phpunit.xml.dist export-ignore diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..ef7eb61 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [nyholm, jderusse] diff --git a/.github/workflows/.editorconfig b/.github/workflows/.editorconfig new file mode 100644 index 0000000..7bd3346 --- /dev/null +++ b/.github/workflows/.editorconfig @@ -0,0 +1,2 @@ +[*.yml] +indent_size = 2 diff --git a/.github/workflows/branch_alias.yml b/.github/workflows/branch_alias.yml new file mode 100644 index 0000000..6c27511 --- /dev/null +++ b/.github/workflows/branch_alias.yml @@ -0,0 +1,76 @@ +name: Update branch alias + +on: + push: + tags: ['*'] + +jobs: + branch-alias: + name: Update branch alias + runs-on: ubuntu-latest + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + coverage: none + + - name: Find branch alias + id: find_alias + run: | + TAG=$(echo $GITHUB_REF | cut -d'/' -f 3) + echo "Last tag was $TAG" + ARR=(${TAG//./ }) + ARR[1]=$((${ARR[1]}+1)) + echo ::set-output name=alias::${ARR[0]}.${ARR[1]} + + - name: Checkout main repo + run: | + git clone --branch master https://${{ secrets.BOT_GITHUB_TOKEN }}:x-oauth-basic@github.com/async-aws/aws aws + + - name: Update branch alias + run: | + cd aws/src/Service/XRay + CURRENT_ALIAS=$(composer config extra.branch-alias.dev-master | cut -d'-' -f 1) + + # If there is a current value on the branch alias + if [ ! -z $CURRENT_ALIAS ]; then + NEW_ALIAS=${{ steps.find_alias.outputs.alias }} + CURRENT_ARR=(${CURRENT_ALIAS//./ }) + NEW_ARR=(${NEW_ALIAS//./ }) + + if [ ${CURRENT_ARR[0]} -gt ${NEW_ARR[0]} ]; then + echo "The current value for major version is larger" + exit 1; + fi + + if [ ${CURRENT_ARR[0]} -eq ${NEW_ARR[0]} ] && [ ${CURRENT_ARR[1]} -gt ${NEW_ARR[1]} ]; then + echo "The current value for minor version is larger" + exit 1; + fi + fi + + composer config extra.branch-alias.dev-master ${{ steps.find_alias.outputs.alias }}-dev + + - name: Commit & push the new files + run: | + echo "::group::git status" + cd aws + git status + echo "::endgroup::" + + git add -N . + if [[ $(git diff --numstat | wc -l) -eq 0 ]]; then + echo "No changes found. Exiting." + exit 0; + fi + + git config --local user.email "github@async-aws.com" + git config --local user.name "AsyncAws Bot" + + echo "::group::git push" + git add . + git commit -m "Update branch alias" + git push + echo "::endgroup::" diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..1a72276 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,27 @@ +name: BC Check + +on: + push: + branches: + - master + +jobs: + roave-bc-check: + name: Roave BC Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Modify composer.json + run: | + sed -i -re 's/"require": \{/"minimum-stability": "dev","prefer-stable": true,"require": \{/' composer.json + cat composer.json + + git config --local user.email "github@async-aws.com" + git config --local user.name "AsyncAws Bot" + git commit -am "Allow unstable dependencies" + + - name: Roave BC Check + uses: docker://nyholm/roave-bc-check-ga diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1f7c850 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: Tests + +on: + push: + branches: + - master + +jobs: + + build: + name: Build + runs-on: ubuntu-latest + strategy: + max-parallel: 10 + matrix: + php: ['7.2', '7.3', '7.4', '8.0'] + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Initialize tests + run: make initialize + + - name: Download dependencies + run: | + composer config minimum-stability dev + composer req symfony/phpunit-bridge --no-update + composer update --no-interaction --prefer-dist --optimize-autoloader --prefer-stable + + - name: Run tests + run: ./vendor/bin/simple-phpunit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ef8091 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +*.cache +composer.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..457d417 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Change Log + +## NOT RELEASED + +## 0.1.0 + +First version diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..50402d9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Jérémy Derussé, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..850dffc --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +.EXPORT_ALL_VARIABLES: + +initialize: start-docker +start-docker: + echo "Noop" + +test: initialize + ./vendor/bin/simple-phpunit + +clean: stop-docker +stop-docker: + echo "Noop" diff --git a/README.md b/README.md new file mode 100644 index 0000000..36196f9 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# AsyncAws X-Ray Client + +![](https://github.com/async-aws/x-ray/workflows/Tests/badge.svg?branch=master) +![](https://github.com/async-aws/x-ray/workflows/BC%20Check/badge.svg?branch=master) + +An API client for XRay. + +## Install + +```cli +composer require async-aws/x-ray +``` + +## Documentation + +See https://async-aws.com/clients/x-ray.html for documentation. + +## Contribute + +Contributions are welcome and appreciated. Please read https://async-aws.com/contribute/ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..76df50c --- /dev/null +++ b/composer.json @@ -0,0 +1,33 @@ +{ + "name": "async-aws/x-ray", + "type": "library", + "description": "XRay client, part of the AWS SDK provided by AsyncAws.", + "keywords": [ + "aws", + "amazon", + "sdk", + "async-aws", + "x-ray" + ], + "license": "MIT", + "require": { + "php": "^7.2.5 || ^8.0", + "ext-json": "*", + "async-aws/core": "^1.9" + }, + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, + "autoload": { + "psr-4": { + "AsyncAws\\XRay\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "AsyncAws\\XRay\\Tests\\": "tests/" + } + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..9894ce3 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,23 @@ + + + + + ./src + + + + + + + + ./tests/ + + + diff --git a/src/Exception/InvalidRequestException.php b/src/Exception/InvalidRequestException.php new file mode 100644 index 0000000..eaa85ea --- /dev/null +++ b/src/Exception/InvalidRequestException.php @@ -0,0 +1,21 @@ +toArray(false); + + if (null !== $v = (isset($data['message']) ? (string) $data['message'] : null)) { + $this->message = $v; + } + } +} diff --git a/src/Exception/ThrottledException.php b/src/Exception/ThrottledException.php new file mode 100644 index 0000000..4f28864 --- /dev/null +++ b/src/Exception/ThrottledException.php @@ -0,0 +1,21 @@ +toArray(false); + + if (null !== $v = (isset($data['message']) ? (string) $data['message'] : null)) { + $this->message = $v; + } + } +} diff --git a/src/Input/PutTraceSegmentsRequest.php b/src/Input/PutTraceSegmentsRequest.php new file mode 100644 index 0000000..7456e24 --- /dev/null +++ b/src/Input/PutTraceSegmentsRequest.php @@ -0,0 +1,94 @@ +traceSegmentDocuments = $input['TraceSegmentDocuments'] ?? null; + parent::__construct($input); + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + /** + * @return string[] + */ + public function getTraceSegmentDocuments(): array + { + return $this->traceSegmentDocuments ?? []; + } + + /** + * @internal + */ + public function request(): Request + { + // Prepare headers + $headers = ['content-type' => 'application/json']; + + // Prepare query + $query = []; + + // Prepare URI + $uriString = '/TraceSegments'; + + // Prepare Body + $bodyPayload = $this->requestBody(); + $body = empty($bodyPayload) ? '{}' : json_encode($bodyPayload, 4194304); + + // Return the Request + return new Request('POST', $uriString, $query, $headers, StreamFactory::create($body)); + } + + /** + * @param string[] $value + */ + public function setTraceSegmentDocuments(array $value): self + { + $this->traceSegmentDocuments = $value; + + return $this; + } + + private function requestBody(): array + { + $payload = []; + if (null === $v = $this->traceSegmentDocuments) { + throw new InvalidArgument(sprintf('Missing parameter "TraceSegmentDocuments" for "%s". The value cannot be null.', __CLASS__)); + } + + $index = -1; + $payload['TraceSegmentDocuments'] = []; + foreach ($v as $listValue) { + ++$index; + $payload['TraceSegmentDocuments'][$index] = $listValue; + } + + return $payload; + } +} diff --git a/src/Result/PutTraceSegmentsResult.php b/src/Result/PutTraceSegmentsResult.php new file mode 100644 index 0000000..1f78803 --- /dev/null +++ b/src/Result/PutTraceSegmentsResult.php @@ -0,0 +1,49 @@ +initialize(); + + return $this->unprocessedTraceSegments; + } + + protected function populateResult(Response $response): void + { + $data = $response->toArray(); + + $this->unprocessedTraceSegments = empty($data['UnprocessedTraceSegments']) ? [] : $this->populateResultUnprocessedTraceSegmentList($data['UnprocessedTraceSegments']); + } + + /** + * @return UnprocessedTraceSegment[] + */ + private function populateResultUnprocessedTraceSegmentList(array $json): array + { + $items = []; + foreach ($json as $item) { + $items[] = new UnprocessedTraceSegment([ + 'Id' => isset($item['Id']) ? (string) $item['Id'] : null, + 'ErrorCode' => isset($item['ErrorCode']) ? (string) $item['ErrorCode'] : null, + 'Message' => isset($item['Message']) ? (string) $item['Message'] : null, + ]); + } + + return $items; + } +} diff --git a/src/ValueObject/UnprocessedTraceSegment.php b/src/ValueObject/UnprocessedTraceSegment.php new file mode 100644 index 0000000..d7e1b80 --- /dev/null +++ b/src/ValueObject/UnprocessedTraceSegment.php @@ -0,0 +1,58 @@ +id = $input['Id'] ?? null; + $this->errorCode = $input['ErrorCode'] ?? null; + $this->message = $input['Message'] ?? null; + } + + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + public function getErrorCode(): ?string + { + return $this->errorCode; + } + + public function getId(): ?string + { + return $this->id; + } + + public function getMessage(): ?string + { + return $this->message; + } +} diff --git a/src/XRayClient.php b/src/XRayClient.php new file mode 100644 index 0000000..d49a2e1 --- /dev/null +++ b/src/XRayClient.php @@ -0,0 +1,124 @@ +getResponse($input->request(), new RequestContext(['operation' => 'PutTraceSegments', 'region' => $input->getRegion(), 'exceptionMapping' => [ + 'InvalidRequestException' => InvalidRequestException::class, + 'ThrottledException' => ThrottledException::class, + ]])); + + return new PutTraceSegmentsResult($response); + } + + protected function getAwsErrorFactory(): AwsErrorFactoryInterface + { + return new JsonRestAwsErrorFactory(); + } + + protected function getEndpointMetadata(?string $region): array + { + if (null === $region) { + $region = Configuration::DEFAULT_REGION; + } + + switch ($region) { + case 'cn-north-1': + case 'cn-northwest-1': + return [ + 'endpoint' => "https://xray.$region.amazonaws.com.cn", + 'signRegion' => $region, + 'signService' => 'xray', + 'signVersions' => ['v4'], + ]; + case 'us-gov-east-1': + case 'us-gov-west-1': + return [ + 'endpoint' => "https://xray.$region.amazonaws.com", + 'signRegion' => $region, + 'signService' => 'xray', + 'signVersions' => ['v4'], + ]; + case 'fips-us-east-1': + return [ + 'endpoint' => 'https://xray-fips.us-east-1.amazonaws.com', + 'signRegion' => 'us-east-1', + 'signService' => 'xray', + 'signVersions' => ['v4'], + ]; + case 'fips-us-east-2': + return [ + 'endpoint' => 'https://xray-fips.us-east-2.amazonaws.com', + 'signRegion' => 'us-east-2', + 'signService' => 'xray', + 'signVersions' => ['v4'], + ]; + case 'fips-us-gov-east-1': + return [ + 'endpoint' => 'https://xray-fips.us-gov-east-1.amazonaws.com', + 'signRegion' => 'us-gov-east-1', + 'signService' => 'xray', + 'signVersions' => ['v4'], + ]; + case 'fips-us-gov-west-1': + return [ + 'endpoint' => 'https://xray-fips.us-gov-west-1.amazonaws.com', + 'signRegion' => 'us-gov-west-1', + 'signService' => 'xray', + 'signVersions' => ['v4'], + ]; + case 'fips-us-west-1': + return [ + 'endpoint' => 'https://xray-fips.us-west-1.amazonaws.com', + 'signRegion' => 'us-west-1', + 'signService' => 'xray', + 'signVersions' => ['v4'], + ]; + case 'fips-us-west-2': + return [ + 'endpoint' => 'https://xray-fips.us-west-2.amazonaws.com', + 'signRegion' => 'us-west-2', + 'signService' => 'xray', + 'signVersions' => ['v4'], + ]; + } + + return [ + 'endpoint' => "https://xray.$region.amazonaws.com", + 'signRegion' => $region, + 'signService' => 'xray', + 'signVersions' => ['v4'], + ]; + } +} diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/tests/Integration/XRayClientTest.php b/tests/Integration/XRayClientTest.php new file mode 100644 index 0000000..f280b08 --- /dev/null +++ b/tests/Integration/XRayClientTest.php @@ -0,0 +1,44 @@ +getClient(); + + $input = new PutTraceSegmentsRequest([ + 'TraceSegmentDocuments' => [ + json_encode([ + 'name' => 'service-foo', + 'id' => '1111111111111111', + 'trace_id' => '1-58406520-a006649127e371903a2de979', + 'start_time' => 1480615200.010, + 'end_time' => 1480615200.090, + ]), + ], + ]); + $result = $client->putTraceSegments($input); + + $result->resolve(); + + self::assertSame([], $result->getUnprocessedTraceSegments()); + } + + private function getClient(): XRayClient + { + self::fail('Not implemented'); + + return new XRayClient([ + 'endpoint' => 'http://localhost', + ], new NullProvider()); + } +} diff --git a/tests/Unit/Input/PutTraceSegmentsRequestTest.php b/tests/Unit/Input/PutTraceSegmentsRequestTest.php new file mode 100644 index 0000000..72e88dd --- /dev/null +++ b/tests/Unit/Input/PutTraceSegmentsRequestTest.php @@ -0,0 +1,38 @@ + [ + json_encode([ + 'name' => 'service-foo', + 'id' => '1111111111111111', + 'trace_id' => '1-58406520-a006649127e371903a2de979', + 'start_time' => 1480615200.010, + 'end_time' => 1480615200.090, + ]), + ], + ]); + + // see https://docs.aws.amazon.com/xray/latest/api/API_PutTraceSegments.html + $expected = ' + POST /TraceSegments HTTP/1.0 + Content-Type: application/json + + { + "TraceSegmentDocuments": [ + "{\"name\":\"service-foo\",\"id\":\"1111111111111111\",\"trace_id\":\"1-58406520-a006649127e371903a2de979\",\"start_time\":1480615200.01,\"end_time\":1480615200.09}" + ] + } + '; + + self::assertRequestEqualsHttpRequest($expected, $input->request()); + } +} diff --git a/tests/Unit/Result/PutTraceSegmentsResultTest.php b/tests/Unit/Result/PutTraceSegmentsResultTest.php new file mode 100644 index 0000000..506ad23 --- /dev/null +++ b/tests/Unit/Result/PutTraceSegmentsResultTest.php @@ -0,0 +1,39 @@ +request('POST', 'http://localhost'), $client, new NullLogger())); + $unprocessedTraceSegments = $result->getUnprocessedTraceSegments(); + + self::assertIsArray($unprocessedTraceSegments); + self::assertCount(1, $unprocessedTraceSegments); + self::assertInstanceOf(UnprocessedTraceSegment::class, $unprocessedTraceSegments[0]); + self::assertSame('1', $unprocessedTraceSegments[0]->getErrorCode()); + self::assertSame('1111111111111111', $unprocessedTraceSegments[0]->getId()); + self::assertSame('Error', $unprocessedTraceSegments[0]->getMessage()); + } +} diff --git a/tests/Unit/XRayClientTest.php b/tests/Unit/XRayClientTest.php new file mode 100644 index 0000000..0253aac --- /dev/null +++ b/tests/Unit/XRayClientTest.php @@ -0,0 +1,34 @@ + [ + json_encode([ + 'name' => 'service-foo', + 'id' => '1111111111111111', + 'trace_id' => '1-58406520-a006649127e371903a2de979', + 'start_time' => 1480615200.010, + 'end_time' => 1480615200.090, + ]), + ], + ]); + $result = $client->putTraceSegments($input); + + self::assertInstanceOf(PutTraceSegmentsResult::class, $result); + self::assertFalse($result->info()['resolved']); + } +}