diff --git a/builder/gen-dockerfile/src/Builder/GenFilesCommand.php b/builder/gen-dockerfile/src/Builder/GenFilesCommand.php index 7432ae88..7476daff 100644 --- a/builder/gen-dockerfile/src/Builder/GenFilesCommand.php +++ b/builder/gen-dockerfile/src/Builder/GenFilesCommand.php @@ -261,10 +261,15 @@ public function createDockerfile($baseImage) ); } if (self::isStackdriverIntegrationEnabled($envs)) { - ValidateGoogleCloud::doCheck($this->workspace); - $enableStackdriverCmd = 'RUN /bin/bash /stackdriver-files/' - . 'enable_stackdriver_integration.sh'; $envs['IS_BATCH_DAEMON_RUNNING'] = 'true'; + $result = ValidateGoogleCloud::doCheck($this->workspace); + if ($result == ValidateGoogleCloud::FOUND_GOOGLE_CLOUD) { + $enableStackdriverCmd = 'RUN /bin/bash /stackdriver-files/' + . 'enable_stackdriver_integration.sh'; + } else { + $enableStackdriverCmd = 'RUN /bin/bash /stackdriver-files/' + . 'enable_stackdriver_integration.sh --individual'; + } } else { $enableStackdriverCmd = ''; } diff --git a/builder/gen-dockerfile/src/ValidateGoogleCloud.php b/builder/gen-dockerfile/src/ValidateGoogleCloud.php index 83cad1df..563f8805 100644 --- a/builder/gen-dockerfile/src/ValidateGoogleCloud.php +++ b/builder/gen-dockerfile/src/ValidateGoogleCloud.php @@ -23,11 +23,16 @@ class ValidateGoogleCloud { - const MINIMUM_VERSION = 'v0.33'; + const MINIMUM_GOOGLE_CLOUD_VERSION = 'v0.33'; + const MINIMUM_GOOGLE_LOGGING_VERSION = 'v1.3.0'; + const MINIMUM_GOOGLE_ER_VERSION = 'v0.4.0'; + const FOUND_GOOGLE_CLOUD = 1; + const FOUND_INDIVIDUAL_PACKAGES = 2; /* * @param string $workspace - * @return bool + * @return int return FOUND_GOOGLE_CLOUD when we found google/cloud, + * otherwise return FOUND_INDIVIDUAL_PACKAGES * @throw GoogleCloudVersionException */ public static function doCheck($workspace) @@ -39,52 +44,85 @@ public static function doCheck($workspace) ); } $composer = json_decode(file_get_contents($filename), true); - if (is_array($composer) - && array_key_exists('require', $composer) - && array_key_exists('google/cloud', $composer['require'])) { - $constraints = $composer['require']['google/cloud']; + $constraintsMap = []; + $minimumVersionMap = [ + 'google/cloud' => self::MINIMUM_GOOGLE_CLOUD_VERSION, + 'google/cloud-logging' => self::MINIMUM_GOOGLE_LOGGING_VERSION, + 'google/cloud-error-reporting' => self::MINIMUM_GOOGLE_ER_VERSION + ]; + // Make sure there is `require` field in `composer.json`. + if (!(is_array($composer) && array_key_exists('require', $composer))) { + throw new GoogleCloudVersionException( + 'Required packages not found in composer.json. ' + . 'Consider running `composer require google/cloud`' + ); + } + // For google/cloud. + if (array_key_exists('google/cloud', $composer['require'])) { + $constraintsMap['google/cloud'] = + $composer['require']['google/cloud']; + } elseif (array_key_exists('google/cloud-logging', + $composer['require']) && + array_key_exists('google/cloud-error-reporting', + $composer['require'])) { + // For cloud-logging and cloud-error-reporting. + $constraintsMap['google/cloud-logging'] = + $composer['require']['google/cloud-logging']; + $constraintsMap['google/cloud-error-reporting'] = + $composer['require']['google/cloud-error-reporting']; } else { throw new GoogleCloudVersionException( - 'google/cloud not found in composer.json' + 'Required packages not found in composer.json. ' + . 'Consider running `composer require google/cloud`' ); } - $versions = self::getCurrentGoogleCloudVersions(); + // Now we have $constraintsMap. All should have at least the minimum + // version. - // Check all the available versions against the constraints - // and returns matched ones - $filtered = Semver::satisfiedBy($versions, $constraints); + foreach ($constraintsMap as $package => $constraints) { + $versions = self::getCurrentPackageVersions($package); - if (count($filtered) === 0) { - throw new GoogleCloudVersionException( - 'no available matching version of google/cloud' - ); - } - foreach ($filtered as $version) { - if (Comparator::lessThan($version, self::MINIMUM_VERSION)) { + // Check all the available versions against the constraints + // and returns matched ones + $filtered = Semver::satisfiedBy($versions, $constraints); + if (count($filtered) === 0) { throw new GoogleCloudVersionException( - 'stackdriver integration needs google/cloud ' - . self::MINIMUM_VERSION . ' or higher' + "no available matching version of $package" ); } + foreach ($filtered as $version) { + if (Comparator::lessThan($version, $minimumVersionMap[$package])) { + throw new GoogleCloudVersionException( + "stackdriver integration needs $package " + . $minimumVersionMap[$package] . ' or higher' + ); + } + } + } + + if (array_key_exists('google/cloud', $constraintsMap)) { + return self::FOUND_GOOGLE_CLOUD; + } else { + return self::FOUND_INDIVIDUAL_PACKAGES; } - return true; } /** - * Determine available versions for google/cloud + * Determine available versions for a given package. + * @param string $package * @return array */ - private static function getCurrentGoogleCloudVersions() + private static function getCurrentPackageVersions($package) { exec( - 'composer show --all google/cloud |grep \'versions : \'', + "composer show --all $package |grep 'versions : '", $output, $ret ); if ($ret !== 0) { throw new GoogleCloudVersionException( - 'Failed to determine available versions of google/cloud package' + "Failed to determine available versions of $package package" ); } // Remove the title diff --git a/builder/gen-dockerfile/tests/GenFilesCommandTest.php b/builder/gen-dockerfile/tests/GenFilesCommandTest.php index c2273cb7..165bb839 100644 --- a/builder/gen-dockerfile/tests/GenFilesCommandTest.php +++ b/builder/gen-dockerfile/tests/GenFilesCommandTest.php @@ -165,6 +165,43 @@ public function dataProvider() "enable_stackdriver_integration.sh" ] ], + [ + // stackdriver individual packages + __DIR__ . '/test_data/stackdriver_individual', + null, + '', + '/app/web', + 'added by the php runtime builder', + 'gcr.io/google-appengine/php71:latest', + ["GOOGLE_RUNTIME_RUN_COMPOSER_SCRIPT=true \\\n", + "FRONT_CONTROLLER_FILE=index.php \\\n", + "DETECTED_PHP_VERSION=7.1 \\\n", + "IS_BATCH_DAEMON_RUNNING=true \n", + "enable_stackdriver_integration.sh --individual" + ] + ], + [ + // stackdriver old logging + __DIR__ . '/test_data/stackdriver_old_logging', + null, + '', + '/app/web', + 'added by the php runtime builder', + 'gcr.io/google-appengine/php71:latest', + [], + '\\Google\\Cloud\\Runtimes\\Builder\\Exception\\GoogleCloudVersionException' + ], + [ + // stackdriver old error_reporting + __DIR__ . '/test_data/stackdriver_old_er', + null, + '', + '/app/web', + 'added by the php runtime builder', + 'gcr.io/google-appengine/php71:latest', + [], + '\\Google\\Cloud\\Runtimes\\Builder\\Exception\\GoogleCloudVersionException' + ], [ // stackdriver no composer.json __DIR__ . '/test_data/stackdriver_no_composer', diff --git a/builder/gen-dockerfile/tests/test_data/stackdriver_individual/app.yaml b/builder/gen-dockerfile/tests/test_data/stackdriver_individual/app.yaml new file mode 100644 index 00000000..383f6885 --- /dev/null +++ b/builder/gen-dockerfile/tests/test_data/stackdriver_individual/app.yaml @@ -0,0 +1,6 @@ +env: flex +runtime: php + +runtime_config: + enable_stackdriver_integration: true + document_root: web diff --git a/builder/gen-dockerfile/tests/test_data/stackdriver_individual/composer.json b/builder/gen-dockerfile/tests/test_data/stackdriver_individual/composer.json new file mode 100644 index 00000000..d6e4bea4 --- /dev/null +++ b/builder/gen-dockerfile/tests/test_data/stackdriver_individual/composer.json @@ -0,0 +1,6 @@ +{ + "require": { + "google/cloud-logging": "^1.3.1", + "google/cloud-error-reporting": "^0.4.1" + } +} diff --git a/builder/gen-dockerfile/tests/test_data/stackdriver_old_er/app.yaml b/builder/gen-dockerfile/tests/test_data/stackdriver_old_er/app.yaml new file mode 100644 index 00000000..383f6885 --- /dev/null +++ b/builder/gen-dockerfile/tests/test_data/stackdriver_old_er/app.yaml @@ -0,0 +1,6 @@ +env: flex +runtime: php + +runtime_config: + enable_stackdriver_integration: true + document_root: web diff --git a/builder/gen-dockerfile/tests/test_data/stackdriver_old_er/composer.json b/builder/gen-dockerfile/tests/test_data/stackdriver_old_er/composer.json new file mode 100644 index 00000000..7ffa29b7 --- /dev/null +++ b/builder/gen-dockerfile/tests/test_data/stackdriver_old_er/composer.json @@ -0,0 +1,6 @@ +{ + "require": { + "google/cloud-logging": "^1.4.1", + "google/cloud-error-reporting": "^0.3.0" + } +} diff --git a/builder/gen-dockerfile/tests/test_data/stackdriver_old_logging/app.yaml b/builder/gen-dockerfile/tests/test_data/stackdriver_old_logging/app.yaml new file mode 100644 index 00000000..383f6885 --- /dev/null +++ b/builder/gen-dockerfile/tests/test_data/stackdriver_old_logging/app.yaml @@ -0,0 +1,6 @@ +env: flex +runtime: php + +runtime_config: + enable_stackdriver_integration: true + document_root: web diff --git a/builder/gen-dockerfile/tests/test_data/stackdriver_old_logging/composer.json b/builder/gen-dockerfile/tests/test_data/stackdriver_old_logging/composer.json new file mode 100644 index 00000000..1404ebff --- /dev/null +++ b/builder/gen-dockerfile/tests/test_data/stackdriver_old_logging/composer.json @@ -0,0 +1,6 @@ +{ + "require": { + "google/cloud-logging": "^1.2.0", + "google/cloud-error-reporting": "^0.4.1" + } +} diff --git a/php-base/stackdriver-files/enable_stackdriver_integration.sh b/php-base/stackdriver-files/enable_stackdriver_integration.sh index 90effeb2..b7a3d6b8 100644 --- a/php-base/stackdriver-files/enable_stackdriver_integration.sh +++ b/php-base/stackdriver-files/enable_stackdriver_integration.sh @@ -28,5 +28,10 @@ echo "Enabling stackdriver integration..." # To start the batch daemon cp /stackdriver-files/batch-daemon.conf /etc/supervisor/conf.d -# For enabling automatic error reporting -cp /stackdriver-files/stackdriver-errorreporting.ini ${PHP_DIR}/lib/conf.d +if [ "${1}" = "--individual" ]; then + # For enabling automatic error reporting for google/cloud-error-reporting + cp /stackdriver-files/stackdriver-errorreporting-individual.ini ${PHP_DIR}/lib/conf.d +else + # For enabling automatic error reporting for google/cloud + cp /stackdriver-files/stackdriver-errorreporting.ini ${PHP_DIR}/lib/conf.d +fi diff --git a/php-base/stackdriver-files/stackdriver-errorreporting-individual.ini b/php-base/stackdriver-files/stackdriver-errorreporting-individual.ini new file mode 100644 index 00000000..3a0fc6a8 --- /dev/null +++ b/php-base/stackdriver-files/stackdriver-errorreporting-individual.ini @@ -0,0 +1,2 @@ +# Automatic error reporting +auto_prepend_file='/app/vendor/google/cloud-error-reporting/prepend.php' diff --git a/testapps/integration-individual-packages/README.md b/testapps/integration-individual-packages/README.md new file mode 100644 index 00000000..1ab4e885 --- /dev/null +++ b/testapps/integration-individual-packages/README.md @@ -0,0 +1,14 @@ +# Integration Application + +This application can be used for the common Cloud Languages Runtime +Integration Framework. It is primarily used to test Stackdriver +integration with individual packages; `google/cloud-logging` and +`google/cloud-error-reporting`. + +## Tests + +See [Tests](https://github.com/GoogleCloudPlatform/runtimes-common/blob/master/integration_tests/README.md#tests). + +## Authentication + +This application uses Application Default Credentials to authenticate. diff --git a/testapps/integration-individual-packages/app.yaml b/testapps/integration-individual-packages/app.yaml new file mode 100644 index 00000000..3475c92b --- /dev/null +++ b/testapps/integration-individual-packages/app.yaml @@ -0,0 +1,6 @@ +runtime: php +env: flex + +runtime_config: + document_root: web + enable_stackdriver_integration: true diff --git a/testapps/integration-individual-packages/cloudbuild.yaml.in b/testapps/integration-individual-packages/cloudbuild.yaml.in new file mode 100644 index 00000000..05a87222 --- /dev/null +++ b/testapps/integration-individual-packages/cloudbuild.yaml.in @@ -0,0 +1,7 @@ +steps: + - name: gcr.io/cloud-builders/docker + args: ['build', '-t', '${IMAGE}', '.'] + - name: gcr.io/gcp-runtimes/structure_test + args: ['-i', '${IMAGE}', '--config', 'php_default.yaml', '-v'] + - name: gcr.io/gcp-runtimes/integration_test + args: ['-i', '${IMAGE}', '--no-monitoring', '--no-exception'] diff --git a/testapps/integration-individual-packages/composer.json b/testapps/integration-individual-packages/composer.json new file mode 100644 index 00000000..6063f034 --- /dev/null +++ b/testapps/integration-individual-packages/composer.json @@ -0,0 +1,21 @@ +{ + "autoload": { + "psr-4": { + "Google\\Cloud\\Integration\\": ["src", "test/lib"] + } + }, + "require": { + "php": "5.6.*|7.0.*|7.1.*", + "silex/silex": "^1.3", + "google/cloud-logging": "^1.3.1", + "google/cloud-error-reporting": "^0.4.1" + }, + "require-dev": { + "behat/mink": "^1.7", + "behat/mink-goutte-driver": "^1.2", + "phpunit/phpunit": "~4", + "symfony/browser-kit": "^3.0", + "symfony/http-kernel": "^3.0", + "google/cloud-tools": "^0.6" + } +} diff --git a/testapps/integration-individual-packages/runtimes.yaml b/testapps/integration-individual-packages/runtimes.yaml new file mode 100644 index 00000000..eefb9c5e --- /dev/null +++ b/testapps/integration-individual-packages/runtimes.yaml @@ -0,0 +1,6 @@ +schema_version: 1 + +runtimes: + php: + target: + file: test.yaml diff --git a/testapps/integration-individual-packages/test.yaml.in b/testapps/integration-individual-packages/test.yaml.in new file mode 100644 index 00000000..aaeb9930 --- /dev/null +++ b/testapps/integration-individual-packages/test.yaml.in @@ -0,0 +1,7 @@ +steps: + - name: '${STAGING_BUILDER_IMAGE}' + args: ['--php71-image', 'gcr.io/google-appengine/php71:staging', '--php70-image', 'gcr.io/google-appengine/php70:staging', '--php56-image', 'gcr.io/google-appengine/php56:staging'] + - name: 'gcr.io/cloud-builders/docker:latest' + args: ['build', '-t', '$_OUTPUT_IMAGE', '.'] +images: + - '$_OUTPUT_IMAGE' diff --git a/testapps/integration-individual-packages/web/index.php b/testapps/integration-individual-packages/web/index.php new file mode 100644 index 00000000..be03b604 --- /dev/null +++ b/testapps/integration-individual-packages/web/index.php @@ -0,0 +1,84 @@ +get('/', function () { + return 'Hello World!'; +}); + +$app->post('/logging_standard', function (Request $request) { + $token = $request->request->get('token'); + $stderr = fopen('php://stderr', 'w'); + fwrite($stderr, $token . PHP_EOL); + fclose($stderr); + + return 'appengine.googleapis.com%2Fstderr'; +}); + +$app->post('/logging_custom', function (Request $request) { + $logName = $request->request->get('log_name'); + $token = $request->request->get('token'); + $level = $request->request->get('level'); + + $logger = LoggingClient::psrBatchLogger($logName); + $logger->log($level, $token); + + return $logName; +}); + +// This test does not work yet. The monitoring client is NYI. +$app->post('/monitoring', function () { + return 'OK'; +}); + +$app->post('/exception', function (Request $request) { + $token = $request->request->get('token'); + Bootstrap::exceptionHandler(new Exception($token)); + return 'OK'; +}); + +$app->get('/custom', function () { + // No custom tests, so just return an empty map. + return '{}'; +}); + +$app['debug'] = true; + +$app->before(function (Request $request) { + if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) { + $data = json_decode($request->getContent(), true); + $request->request->replace(is_array($data) ? $data : array()); + } +}); + +// @codeCoverageIgnoreStart +if (PHP_SAPI != 'cli') { + $app->run(); +} +// @codeCoverageIgnoreEnd + +return $app; +// [END index_php]