diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fa395cc..138527fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,10 @@ name: ci on: - push: + pull_request: branches: + - develop - master - pull_request: + push: branches: - master @@ -18,7 +19,7 @@ jobs: - 6379:6379 strategy: matrix: - php-versions: ['7.3', '8.0'] + version: ['7.3', '8.0'] steps: - name: Checkout code uses: actions/checkout@v2 @@ -26,7 +27,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php-versions }} + php-version: ${{ matrix.version }} extensions: mbstring, intl ini-values: post_max_size=256M, max_execution_time=180 coverage: xdebug @@ -37,7 +38,49 @@ jobs: composer update composer dumpautoload - - name: script + - name: Build run: | vendor/bin/phpcs --ignore=functions.php --standard=PSR2 src/ vendor/bin/phpunit -c phpunit.xml.dist -v --testsuite integration + + sonarqube: + name: Sonarqube + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: SonarQube Scan (Push) + if: github.event_name == 'push' + uses: SonarSource/sonarcloud-github-action@v1.5 + env: + SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} + with: + projectBaseDir: . + args: > + -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} + -Dsonar.projectName=${{ github.event.repository.name }} + -Dsonar.projectKey=splitsoftware_split-sdk-php + -Dsonar.exclusions="**/tests/**/*.*" + -Dsonar.links.ci="https://github.com/splitio/${{ github.event.repository.name }}/actions" + -Dsonar.links.scm="https://github.com/splitio/${{ github.event.repository.name }}" + + - name: SonarQube Scan (Pull Request) + if: github.event_name == 'pull_request' + uses: SonarSource/sonarcloud-github-action@v1.5 + env: + SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} + with: + projectBaseDir: . + args: > + -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} + -Dsonar.projectName=${{ github.event.repository.name }} + -Dsonar.projectKey=splitsoftware_split-sdk-php + -Dsonar.exclusions="**/tests/**/*.*" + -Dsonar.links.ci="https://github.com/splitio/${{ github.event.repository.name }}/actions" + -Dsonar.links.scm="https://github.com/splitio/${{ github.event.repository.name }}" + -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} + -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} + -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }} diff --git a/CHANGES.txt b/CHANGES.txt index 00964a8c..120d55bc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +7.1.0 (Dec 3, 2021) + - Added a new option to use when running Redis in cluster mode called `keyHashTags` which receives a list of hashtags from which the SDK will randomly pick one to use on the generated instance. + 7.0.0 (Nov 23, 2021) - BREAKING CHANGE: Removed support from versions older than PHP 7.3. - BREAKING CHANGE: Removed SharedMemory (shmop) component for Segments. diff --git a/sonar-scanner.sh b/sonar-scanner.sh deleted file mode 100755 index c82e7fa6..00000000 --- a/sonar-scanner.sh +++ /dev/null @@ -1,41 +0,0 @@ -#/bin/bash -e - -sonar_scanner() { - local params="$@" - - vendor/bin/sonar-scanner \ - -Dsonar.host.url='https://sonarqube.split-internal.com' \ - -Dsonar.login="$SONAR_TOKEN" \ - -Dsonar.ws.timeout='300' \ - -Dsonar.sources='./src' \ - -Dsonar.projectName='php-client' \ - -Dsonar.exclusions='**/tests/**/*.*' \ - -Dsonar.links.ci='https://travis-ci.com/splitio/php-client' \ - -Dsonar.links.scm='https://github.com/splitio/php-client' \ - "${params}" - - return $? -} - -if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - sonar_scanner \ - -Dsonar.pullrequest.provider='GitHub' \ - -Dsonar.pullrequest.github.repository='splitio/php-client' \ - -Dsonar.pullrequest.key=$TRAVIS_PULL_REQUEST \ - -Dsonar.pullrequest.branch=$TRAVIS_PULL_REQUEST_BRANCH \ - -Dsonar.pullrequest.base=$TRAVIS_BRANCH -else - if [ "$TRAVIS_BRANCH" == 'master' ]; then - sonar_scanner \ - -Dsonar.branch.name=$TRAVIS_BRANCH - else - if [ "$TRAVIS_BRANCH" == 'develop' ]; then - TARGET_BRANCH='master' - else - TARGET_BRANCH='develop' - fi - sonar_scanner \ - -Dsonar.branch.name=$TRAVIS_BRANCH \ - -Dsonar.branch.target=$TARGET_BRANCH - fi -fi diff --git a/src/SplitIO/Component/Cache/Storage/Adapter/PRedis.php b/src/SplitIO/Component/Cache/Storage/Adapter/PRedis.php index 620a8850..b3f9d60c 100644 --- a/src/SplitIO/Component/Cache/Storage/Adapter/PRedis.php +++ b/src/SplitIO/Component/Cache/Storage/Adapter/PRedis.php @@ -67,6 +67,19 @@ private function isValidSentinelConfig($sentinels, $options) return true; } + private function validateKeyHashTag($keyHashTag) + { + if (!is_string($keyHashTag)) { + return array('valid' => false, 'msg' => 'keyHashTag must be string.'); + } + if ((strlen($keyHashTag) < 3) || ($keyHashTag[0] != "{") || + (substr($keyHashTag, -1) != "}") || (substr_count($keyHashTag, "{") != 1) || + (substr_count($keyHashTag, "}") != 1)) { + return array('valid' => false, 'msg' => 'keyHashTag is not valid.'); + } + return array('valid' => true, 'msg' => ''); + } + /** * @param mixed $options * @return string @@ -77,19 +90,42 @@ private function getDefaultKeyHashTag($options) if (!isset($options['keyHashTag'])) { return "{SPLITIO}"; } - $keyHashTag = $options['keyHashTag']; - if (!is_string($keyHashTag)) { - throw new AdapterException("keyHashTag must be string."); - } else { - if ((strlen($keyHashTag) < 3) || ($keyHashTag[0] != "{") || - (substr($keyHashTag, -1) != "}") || (substr_count($keyHashTag, "{") != 1) || - (substr_count($keyHashTag, "}") != 1)) { - throw new AdapterException("keyHashTag is not valid."); + $validation = $this->validateKeyHashTag($options['keyHashTag']); + if (!($validation['valid'])) { + throw new AdapterException($validation['msg']); + } + return $options['keyHashTag']; + } + + + /** + * @param mixed $options + * @return string + * @throws AdapterException + */ + private function selectKeyHashTag($options) + { + if (!isset($options['keyHashTags'])) { // check if array keyHashTags is set + return $this->getDefaultKeyHashTag($options); // defaulting to keyHashTag or {SPLITIO} + } + $keyHashTags = $options['keyHashTags']; + $msg = $this->isValidConfigArray($keyHashTags, 'keyHashTags'); // check if is valid array + if (!is_null($msg)) { + throw new AdapterException($msg); + } + $filteredArray = array_filter( // filter to only use string element {X} + $keyHashTags, + function ($value) { + return $this->validateKeyHashTag($value)['valid']; } + ); + if (count($filteredArray) == 0) { + throw new AdapterException('keyHashTags size is zero after filtering valid elements.'); } - return $keyHashTag; + return $selected = $filteredArray[array_rand($filteredArray, 1)]; } + /** * @param array $clusters * @return bool @@ -143,7 +179,7 @@ private function getRedisConfiguration($options) switch ($_options['distributedStrategy']) { case 'cluster': if ($this->isValidClusterConfig($clusters)) { - $keyHashTag = $this->getDefaultKeyHashTag($_options); + $keyHashTag = $this->selectKeyHashTag($_options); $_options['cluster'] = 'redis'; $redisConfigutation['redis'] = $clusters; $prefix = isset($_options['prefix']) ? $_options['prefix'] : ''; diff --git a/src/SplitIO/Version.php b/src/SplitIO/Version.php index 5221d02a..f6d34498 100644 --- a/src/SplitIO/Version.php +++ b/src/SplitIO/Version.php @@ -3,5 +3,5 @@ class Version { - const CURRENT = '7.0.0'; + const CURRENT = '7.1.0'; } diff --git a/tests/Suite/Adapter/RedisAdapterTest.php b/tests/Suite/Adapter/RedisAdapterTest.php index 7bfb440a..7708f6ea 100644 --- a/tests/Suite/Adapter/RedisAdapterTest.php +++ b/tests/Suite/Adapter/RedisAdapterTest.php @@ -401,6 +401,79 @@ public function testRedisWithoutCustomKeyHashtagClusters() $predis->getItem('this_is_a_test_key'); } + public function testRedisWithClustersKeyHashTags() + { + $this->expectException( + 'SplitIO\Component\Cache\Storage\Exception\AdapterException', + "keyHashTags must be array." + ); + $predis = new PRedis(array( + 'clusterNodes' => array( + 'tcp://MYIP:26379?timeout=3' + ), + 'options' => array( + 'distributedStrategy' => 'cluster', + 'keyHashTags' => '{TEST}' + ) + )); + + $predis->getItem('this_is_a_test_key'); + } + + public function testRedisWithClustersKeyHashTagsInvalid() + { + $this->expectException( + 'SplitIO\Component\Cache\Storage\Exception\AdapterException', + "keyHashTags size is zero after filtering valid elements." + ); + $predis = new PRedis(array( + 'clusterNodes' => array( + 'tcp://MYIP:26379?timeout=3' + ), + 'options' => array( + 'distributedStrategy' => 'cluster', + 'keyHashTags' => array(1, 2) + ) + )); + + $predis->getItem('this_is_a_test_key'); + } + + public function testRedisWithClustersKeyHashTagsInvalidHashTags() + { + $this->expectException( + 'SplitIO\Component\Cache\Storage\Exception\AdapterException', + "keyHashTags size is zero after filtering valid elements." + ); + $predis = new PRedis(array( + 'clusterNodes' => array( + 'tcp://MYIP:26379?timeout=3' + ), + 'options' => array( + 'distributedStrategy' => 'cluster', + 'keyHashTags' => array("one", "two", "three") + ) + )); + + $predis->getItem('this_is_a_test_key'); + } + + public function testRedisWithClustersKeyHashTagsValid() + { + $this->expectException('\Predis\ClientException'); + $predis = new PRedis(array( + 'clusterNodes' => array( + 'tcp://MYIP:26379?timeout=3' + ), + 'options' => array( + 'distributedStrategy' => 'cluster', + 'keyHashTags' => array("{one}", "{two}", "{three}") + ) + )); + + $predis->getItem('this_is_a_test_key'); + } + public function testRedisSSLWithClusterFails() { $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException');