diff --git a/.clang-tidy b/.clang-tidy index 352da4ef..3a5ab5e8 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -39,6 +39,8 @@ # meaningless). It would also warn if `F` accepts by `const&`, which is # another detail that the caller need not care about. # +# -bugprone-easily-swappable-parameters: Too many false positives. +# Checks: > -*, bugprone-*, @@ -57,7 +59,8 @@ Checks: > -modernize-use-trailing-return-type, -modernize-return-braced-init-list, -modernize-avoid-c-arrays, - -performance-move-const-arg + -performance-move-const-arg, + -bugprone-easily-swappable-parameters # Turn all the warnings from the checks above into errors. WarningsAsErrors: "*" @@ -87,3 +90,4 @@ CheckOptions: - { key: readability-identifier-naming.StaticConstantPrefix, value: k } - { key: readability-implicit-bool-conversion.AllowIntegerConditions, value: 1 } - { key: readability-implicit-bool-conversion.AllowPointerConditions, value: 1 } + - { key: readability-function-cognitive-complexity.IgnoreMacros, value: 1 } diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 01adf846..df09849e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,8 +10,8 @@ env: jobs: build-ubuntu-focal: - name: ubuntu-20.04 - runs-on: ubuntu-20.04 + name: ubuntu-22.04 + runs-on: ubuntu-22.04 steps: - name: install ninja run: sudo apt install ninja-build @@ -42,6 +42,13 @@ jobs: cmake -S . -B "${{runner.temp}}/build" -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_TOOLCHAIN_FILE="${{runner.temp}}/vcpkg/scripts/buildsystems/vcpkg.cmake" + - uses: actions/cache/save@v3 + if: always() + with: + path: | + ~/.cache/vcpkg + ~/.cache/bin + key: vcpkg-${{ env.vcpkg_SHA }}-build-ubuntu-focal-${{ hashFiles('vcpkg.json') }} - name: build run: cmake --build "${{runner.temp}}/build" - name: test @@ -51,8 +58,8 @@ jobs: # Verify the howto guides compile correctly, they need a special setup to use the # current version of `functions-framework-cpp` through vcpkg. build-ubuntu-focal-howto: - name: ubuntu-20.04 - runs-on: ubuntu-20.04 + name: ubuntu-22.04 + runs-on: ubuntu-22.04 steps: - name: install ninja run: sudo apt install ninja-build @@ -82,6 +89,12 @@ jobs: run: > cmake -S examples/site/howto_local_development -B "${{runner.temp}}/howto_local_development/build" -GNinja -DCMAKE_TOOLCHAIN_FILE=/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake + - uses: actions/cache/save@v3 + if: always() + with: + path: | + ~/.cache/vcpkg + key: vcpkg-${{ env.vcpkg_SHA }}-${{ runner.os }}-${{ hashFiles('vcpkg.json') }} - name: build-local-development env: VCPKG_OVERLAY_PORTS: "${{runner.temp}}/quickstart/vcpkg-overlays" @@ -89,6 +102,7 @@ jobs: cmake --build "${{runner.temp}}/howto_local_development/build" build-msvc-2019: + if: ${{ false }} # TODO(#366) - disable for now name: msvc-2019 runs-on: windows-2019 steps: @@ -126,6 +140,12 @@ jobs: '-DBUILD_TESTING=ON' ` '-DFUNCTIONS_FRAMEWORK_CPP_TEST_EXAMPLES=OFF' ` '-DCMAKE_TOOLCHAIN_FILE=${{runner.temp}}/vcpkg/scripts/buildsystems/vcpkg.cmake' + - uses: actions/cache/save@v3 + if: always() + with: + path: | + ~\AppData\Local\vcpkg\archives + key: vcpkg-${{ env.vcpkg_SHA }}-build-msvc-2019-2-${{ hashFiles('vcpkg.json') }} - name: build run: cmake --build "${{runner.temp}}/build" - name: test @@ -133,6 +153,7 @@ jobs: run: ctest --output-on-failure --timeout=60s build-macos: + if: ${{ false }} # TODO(#367) - disable for now name: macos-10 runs-on: macos-10.15 steps: @@ -165,6 +186,13 @@ jobs: cmake -S . -B "${{runner.temp}}/build" -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_TOOLCHAIN_FILE="${{runner.temp}}/vcpkg/scripts/buildsystems/vcpkg.cmake" + - uses: actions/cache/save@v3 + if: always() + with: + path: | + ~/.cache/vcpkg + ~/.cache/bin + key: vcpkg-${{ env.vcpkg_SHA }}-build-macos-10-${{ hashFiles('vcpkg.json') }} - name: build run: cmake --build "${{runner.temp}}/build" - name: test diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index d6251621..a22257f2 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -11,7 +11,7 @@ env: jobs: coverage: name: coverage - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: install tools run: sudo apt install ninja-build lcov @@ -43,6 +43,13 @@ jobs: -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_TOOLCHAIN_FILE="${{runner.temp}}/vcpkg/scripts/buildsystems/vcpkg.cmake" + - uses: actions/cache/save@v3 + if: always() + with: + path: | + ~/.cache/vcpkg + ~/.cache/bin + key: vcpkg-${{ env.vcpkg_SHA }}-coverage-${{ hashFiles('vcpkg.json') }} - name: build run: cmake --build ${{runner.workspace}}/build - name: test @@ -52,10 +59,10 @@ jobs: - name: Setup Go uses: actions/setup-go@v2 with: - go-version: '1.13' + go-version: '^1.16' - name: Run Cloud Event conformance tests - uses: GoogleCloudPlatform/functions-framework-conformance/action@v1.0.0 + uses: GoogleCloudPlatform/functions-framework-conformance/action@v1.8.3 with: functionType: 'cloudevent' useBuildpacks: false @@ -63,7 +70,7 @@ jobs: cmd: '${{runner.workspace}}/build/google/cloud/functions/integration_tests/cloud_event_conformance' - name: Run HTTP conformance tests - uses: GoogleCloudPlatform/functions-framework-conformance/action@v1.0.0 + uses: GoogleCloudPlatform/functions-framework-conformance/action@v1.8.3 with: functionType: 'http' useBuildpacks: false diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml index 8ed291fc..ad195add 100644 --- a/.github/workflows/install.yaml +++ b/.github/workflows/install.yaml @@ -7,20 +7,11 @@ on: jobs: static: - name: ubuntu-20.04 - runs-on: ubuntu-20.04 + name: ubuntu-22.04 + runs-on: ubuntu-22.04 steps: - name: install-dependencies - run: sudo apt install ninja-build libboost-dev libboost-program-options-dev nlohmann-json3-dev - - name: install-abseil - run: > - curl -sSL https://github.com/abseil/abseil-cpp/archive/20200225.2.tar.gz | - tar -xzf - --strip-components=1 && - sed -i 's/^#define ABSL_OPTION_USE_\(.*\) 1/#define ABSL_OPTION_USE_\1 0/' "absl/base/options.h" && - cmake -GNinja -DCMAKE_BUILD_TYPE="Release" -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=yes -H. -Bcmake-out/abseil && - cmake --build cmake-out/abseil && - sudo cmake --build cmake-out/abseil --target install && - sudo ldconfig + run: sudo apt install ninja-build libboost-dev libboost-program-options-dev nlohmann-json3-dev libabsl-dev - uses: actions/checkout@v2 - name: configure run: > @@ -40,20 +31,12 @@ jobs: run: "${{runner.temp}}/test_install/build/test_install" shared: - name: ubuntu-20.04-shared - runs-on: ubuntu-20.04 + name: ubuntu-22.04-shared + runs-on: ubuntu-22.04 steps: - name: install-dependencies - run: sudo apt install ninja-build libboost-dev libboost-program-options-dev nlohmann-json3-dev - - name: install-abseil run: > - curl -sSL https://github.com/abseil/abseil-cpp/archive/20200225.2.tar.gz | - tar -xzf - --strip-components=1 && - sed -i 's/^#define ABSL_OPTION_USE_\(.*\) 1/#define ABSL_OPTION_USE_\1 0/' "absl/base/options.h" && - cmake -GNinja -DCMAKE_BUILD_TYPE="Release" -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=yes -H. -Bcmake-out/abseil && - cmake --build cmake-out/abseil && - sudo cmake --build cmake-out/abseil --target install && - sudo ldconfig + sudo apt install ninja-build libboost-dev libboost-program-options-dev nlohmann-json3-dev libabsl-dev - uses: actions/checkout@v2 - name: configure run: > diff --git a/.github/workflows/sanitize.yaml b/.github/workflows/sanitize.yaml index 56c25452..bed854c7 100644 --- a/.github/workflows/sanitize.yaml +++ b/.github/workflows/sanitize.yaml @@ -14,7 +14,7 @@ jobs: # the web UI (e.g. "sanitize / (address)") than # any other alternative we tried. name: " " - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: sanitizer: ["address", "undefined", "thread"] @@ -47,12 +47,19 @@ jobs: # -DGRPC_* flags. run: > cmake -S . -B "${{runner.temp}}/build" -GNinja - -DCMAKE_CXX_COMPILER=clang++-10 - -DCMAKE_C_COMPILER=clang-10 + -DCMAKE_CXX_COMPILER=clang++-14 + -DCMAKE_C_COMPILER=clang-14 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fsanitize=${{matrix.sanitizer}} -DGRPC_TSAN_SUPPRESSED -DGRPC_ASAN_SUPPRESSED" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_TOOLCHAIN_FILE="${{runner.temp}}/vcpkg/scripts/buildsystems/vcpkg.cmake" + - uses: actions/cache/save@v3 + if: always() + with: + path: | + ~/.cache/vcpkg + ~/.cache/bin + key: vcpkg-${{ env.vcpkg_SHA }}-sanitize-${{ matrix.sanitizer }}-${{ hashFiles('vcpkg.json') }} - name: -fsanitize=${{matrix.sanitizer}} / build run: cmake --build "${{runner.temp}}/build" - name: -fsanitize=${{matrix.sanitizer}} / test diff --git a/.github/workflows/style.yaml b/.github/workflows/style.yaml index 61b88cf5..8cbc9438 100644 --- a/.github/workflows/style.yaml +++ b/.github/workflows/style.yaml @@ -11,10 +11,10 @@ env: jobs: clang-tidy: name: clang-tidy - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: install ninja - run: sudo apt install ninja-build clang-tidy-10 + run: sudo apt install ninja-build clang-tidy-14 - uses: actions/checkout@v2 - name: clone-vcpkg working-directory: "${{runner.temp}}" @@ -41,22 +41,28 @@ jobs: cmake -S . -B "${{runner.temp}}/build" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_TOOLCHAIN_FILE="${{runner.temp}}/vcpkg/scripts/buildsystems/vcpkg.cmake" + - uses: actions/cache/save@v3 + if: always() + with: + path: | + ~/.cache/vcpkg + ~/.cache/bin + key: vcpkg-${{ env.vcpkg_SHA }}-style-clang-tidy-${{ hashFiles('vcpkg.json') }} - name: tidy run: > - git ls-files -z | - grep -zE '\.cc$' | - xargs --verbose -P 2 -n 1 -0 clang-tidy-10 -p="${{runner.temp}}/build" + git ls-files -z -- '*.cc' | + xargs --verbose -P 2 -n 1 -0 clang-tidy-14 -p="${{runner.temp}}/build" werror-build: # Using a blank name produces better output on # the web UI than any other alternative we tried. name: " " - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: compiler: [ - {"cxx": "clang++-10", "cc": "clang-10"}, - {"cxx": "g++-10", "cc": "gcc-10" }, + {"cxx": "clang++-14", "cc": "clang-14"}, + {"cxx": "g++-11", "cc": "gcc-11" }, ] steps: - name: install ninja @@ -91,6 +97,13 @@ jobs: -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_TOOLCHAIN_FILE="${{runner.temp}}/vcpkg/scripts/buildsystems/vcpkg.cmake" + - uses: actions/cache/save@v3 + if: always() + with: + path: | + ~/.cache/vcpkg + ~/.cache/bin + key: vcpkg-${{ env.vcpkg_SHA }}-werror-${{ matrix.compiler.cxx }}-${{ hashFiles('vcpkg.json') }} - name: compiler=${{matrix.compiler.cxx}} / build run: cmake --build "${{runner.temp}}/build" - name: compiler=${{matrix.compiler.cxx}} / test @@ -99,35 +112,42 @@ jobs: clang-format: name: clang-format - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - name: clang-format - run: git ls-files -z | grep -zE '\.(cc|h)$' | xargs -P 2 -n 50 -0 clang-format-10 -i + run: > + git ls-files -z -- '*.h' '*.cc' | + xargs -P 2 -n 50 -0 clang-format-14 -i - name: check-diff run: git diff --ignore-submodules=all --color --exit-code . cmake-format: name: cmake-format - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - name: install cmake-format run: pip install cmakelang==0.6.13 - name: cmake-format run: > - git ls-files -z | grep -zE '((^|/)CMakeLists\.txt|\.cmake)$' | + git ls-files -z -- '*.cmake' '**/CMakeLists.txt' CMakeLists.txt | xargs -P 2 -n 1 -0 /home/runner/.local/bin/cmake-format -i - name: check-diff run: git diff --ignore-submodules=all --color --exit-code . generated-files: name: generated-files - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 + - name: install ninja + run: sudo apt install moreutils - name: regenerate-build-examples run: > ./ci/generate-build-examples.sh >ci/build-examples.yaml + - name: update-markdown-code-snippets + run: > + ./ci/update-markdown-code-snippets.sh - name: check-diff run: git diff --ignore-submodules=all --color --exit-code . diff --git a/CHANGELOG.md b/CHANGELOG.md index 146aa912..3a200de7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog -## v1.2.0 - TBD +## v1.2.0 - 2023-07 + +* docs: use working buildpacks (#381) +* doc: examples use declarative configuration (#380) (#371) (#370) (#369) (#368) + (#356) +* doc: use modern CMake flags (#374) ## v1.1.0 - 2022-03 diff --git a/CMakeLists.txt b/CMakeLists.txt index 40b2249e..0c442d1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ set(PACKAGE_BUGREPORT "http://github.com/GoogleCloudPlatform/functions-framework-cpp") project( functions-framework-cpp - VERSION 1.1.0 + VERSION 1.2.0 DESCRIPTION "Functions Framework for C++" LANGUAGES CXX) diff --git a/README.md b/README.md index 4673067f..9f03778e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ [CloudEvents]: https://cloudevents.io/ [docs]: docs [examples]: examples -[examples/hello_world/hello_world.cc]: examples/hello_world/hello_world.cc [Google Cloud Run]: https://cloud.google.com/run/docs/quickstarts/build-and-deploy [Google App Engine]: https://cloud.google.com/appengine/docs/go/ @@ -56,22 +55,24 @@ language runtime, but we're working to make running on [Google Cloud Run] as seamless and symmetric an experience as possible for your C++ Functions Framework projects. -The framework allows you to go from: +The framework allows you to go [from][snippet source]: -[examples/hello_world/hello_world.cc] + +[snippet source]: /examples/hello_world/hello_world.cc ```cc -#include -#include +#include -using ::google::cloud::functions::HttpRequest; -using ::google::cloud::functions::HttpResponse; +namespace gcf = ::google::cloud::functions; -HttpResponse HelloWorld(HttpRequest) { // NOLINT - return HttpResponse{} - .set_header("Content-Type", "text/plain") - .set_payload("Hello World\n"); +gcf::Function HelloWorld() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + return gcf::HttpResponse{} + .set_header("Content-Type", "text/plain") + .set_payload("Hello World\n"); + }); } ``` + To: diff --git a/ci/abi-dumps/functions_framework_cpp.expected.abi.dump.gz b/ci/abi-dumps/functions_framework_cpp.expected.abi.dump.gz index abb32d44..e66b40e3 100644 Binary files a/ci/abi-dumps/functions_framework_cpp.expected.abi.dump.gz and b/ci/abi-dumps/functions_framework_cpp.expected.abi.dump.gz differ diff --git a/ci/build-examples.yaml b/ci/build-examples.yaml index cc935bc6..02782f5d 100644 --- a/ci/build-examples.yaml +++ b/ci/build-examples.yaml @@ -14,7 +14,7 @@ steps: args: ['build', '-t', 'pack', '-f', 'build_scripts/pack.Dockerfile', 'build_scripts'] # Create the docker images for the buildpacks builder. - - name: 'gcr.io/kaniko-project/executor:v1.6.0-debug' + - name: 'gcr.io/kaniko-project/executor:v1.12.0' args: [ "--context=dir:///workspace/", "--dockerfile=build_scripts/Dockerfile", @@ -28,7 +28,7 @@ steps: - name: 'gcr.io/cloud-builders/docker' args: ['pull', 'gcr.io/${PROJECT_ID}/ci/run-image:${BUILD_ID}'] - - name: 'gcr.io/kaniko-project/executor:v1.6.0-debug' + - name: 'gcr.io/kaniko-project/executor:v1.12.0' args: [ "--context=dir:///workspace/", "--dockerfile=build_scripts/Dockerfile", @@ -62,7 +62,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'hello-cloud-event' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent', '--env', 'GOOGLE_FUNCTION_TARGET=HelloCloudEvent', '--path', 'examples/hello_cloud_event', 'hello-cloud-event', @@ -72,7 +71,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'hello-from-namespace' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=hello_from_namespace::HelloWorld', '--path', 'examples/hello_from_namespace', 'hello-from-namespace', @@ -82,7 +80,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'hello-from-namespace-rooted' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=::hello_from_namespace::HelloWorld', '--path', 'examples/hello_from_namespace', 'hello-from-namespace-rooted', @@ -92,7 +89,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'hello-from-nested-namespace' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=hello_from_nested_namespace::ns0::ns1::HelloWorld', '--path', 'examples/hello_from_nested_namespace', 'hello-from-nested-namespace', @@ -102,7 +98,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'hello-multiple-sources' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=HelloMultipleSources', '--path', 'examples/hello_multiple_sources', 'hello-multiple-sources', @@ -112,7 +107,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'hello-gcs' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=HelloGcs', '--path', 'examples/hello_gcs', 'hello-gcs', @@ -122,7 +116,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'hello-with-third-party' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=HelloWithThirdParty', '--path', 'examples/hello_with_third_party', 'hello-with-third-party', @@ -132,7 +125,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'hello-world' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=HelloWorld', '--path', 'examples/hello_world', 'hello-world', @@ -142,7 +134,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'hello-world-rooted' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=::HelloWorld', '--path', 'examples/hello_world', 'hello-world-rooted', @@ -152,7 +143,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'howto-use-legacy-code' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=HowtoUseLegacyCode', '--path', 'examples/howto_use_legacy_code', 'howto-use-legacy-code', @@ -163,7 +153,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-bearer_token' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=bearer_token', '--path', 'examples/site/bearer_token', 'site-bearer_token', @@ -172,7 +161,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-concepts_after_response' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=concepts_after_response', '--path', 'examples/site/concepts_after_response', 'site-concepts_after_response', @@ -181,7 +169,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-concepts_after_timeout' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=concepts_after_timeout', '--path', 'examples/site/concepts_after_timeout', 'site-concepts_after_timeout', @@ -190,7 +177,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-concepts_filesystem' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=concepts_filesystem', '--path', 'examples/site/concepts_filesystem', 'site-concepts_filesystem', @@ -199,7 +185,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-concepts_request' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=concepts_request', '--path', 'examples/site/concepts_request', 'site-concepts_request', @@ -208,7 +193,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-concepts_stateless' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=concepts_stateless', '--path', 'examples/site/concepts_stateless', 'site-concepts_stateless', @@ -217,7 +201,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-env_vars' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=env_vars', '--path', 'examples/site/env_vars', 'site-env_vars', @@ -226,7 +209,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-hello_world_error' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=hello_world_error', '--path', 'examples/site/hello_world_error', 'site-hello_world_error', @@ -235,7 +217,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-hello_world_get' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=hello_world_get', '--path', 'examples/site/hello_world_get', 'site-hello_world_get', @@ -244,7 +225,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-hello_world_http' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=hello_world_http', '--path', 'examples/site/hello_world_http', 'site-hello_world_http', @@ -253,7 +233,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-hello_world_pubsub' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent', '--env', 'GOOGLE_FUNCTION_TARGET=hello_world_pubsub', '--path', 'examples/site/hello_world_pubsub', 'site-hello_world_pubsub', @@ -262,7 +241,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-hello_world_storage' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent', '--env', 'GOOGLE_FUNCTION_TARGET=hello_world_storage', '--path', 'examples/site/hello_world_storage', 'site-hello_world_storage', @@ -271,7 +249,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-http_content' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=http_content', '--path', 'examples/site/http_content', 'site-http_content', @@ -280,7 +257,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-http_cors' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=http_cors', '--path', 'examples/site/http_cors', 'site-http_cors', @@ -289,7 +265,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-http_cors_auth' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=http_cors_auth', '--path', 'examples/site/http_cors_auth', 'site-http_cors_auth', @@ -298,7 +273,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-http_form_data' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=http_form_data', '--path', 'examples/site/http_form_data', 'site-http_form_data', @@ -307,7 +281,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-http_method' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=http_method', '--path', 'examples/site/http_method', 'site-http_method', @@ -316,7 +289,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-http_xml' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=http_xml', '--path', 'examples/site/http_xml', 'site-http_xml', @@ -325,7 +297,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-log_helloworld' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=log_helloworld', '--path', 'examples/site/log_helloworld', 'site-log_helloworld', @@ -334,7 +305,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-log_stackdriver' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent', '--env', 'GOOGLE_FUNCTION_TARGET=log_stackdriver', '--path', 'examples/site/log_stackdriver', 'site-log_stackdriver', @@ -343,7 +313,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-pubsub_subscribe' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent', '--env', 'GOOGLE_FUNCTION_TARGET=pubsub_subscribe', '--path', 'examples/site/pubsub_subscribe', 'site-pubsub_subscribe', @@ -352,7 +321,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-tips_gcp_apis' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=tips_gcp_apis', '--path', 'examples/site/tips_gcp_apis', 'site-tips_gcp_apis', @@ -361,7 +329,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-tips_infinite_retries' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent', '--env', 'GOOGLE_FUNCTION_TARGET=tips_infinite_retries', '--path', 'examples/site/tips_infinite_retries', 'site-tips_infinite_retries', @@ -370,7 +337,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-tips_lazy_globals' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=tips_lazy_globals', '--path', 'examples/site/tips_lazy_globals', 'site-tips_lazy_globals', @@ -379,7 +345,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-tips_retry' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent', '--env', 'GOOGLE_FUNCTION_TARGET=tips_retry', '--path', 'examples/site/tips_retry', 'site-tips_retry', @@ -388,7 +353,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-tips_scopes' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=tips_scopes', '--path', 'examples/site/tips_scopes', 'site-tips_scopes', @@ -397,7 +361,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-tutorial_cloud_bigtable' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=tutorial_cloud_bigtable', '--path', 'examples/site/tutorial_cloud_bigtable', 'site-tutorial_cloud_bigtable', @@ -406,7 +369,6 @@ steps: waitFor: ['gcf-builder-ready'] id: 'site-tutorial_cloud_spanner' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=http', '--env', 'GOOGLE_FUNCTION_TARGET=tutorial_cloud_spanner', '--path', 'examples/site/tutorial_cloud_spanner', 'site-tutorial_cloud_spanner', @@ -454,6 +416,7 @@ steps: # Remove the images created by this build. - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' + allowFailure: true entrypoint: 'bash' args: - '-c' @@ -462,13 +425,13 @@ steps: gcloud container images delete -q gcr.io/${PROJECT_ID}/ci/run-image:${BUILD_ID} gcloud container images delete -q gcr.io/${PROJECT_ID}/ci/build-image:${BUILD_ID} gcloud container images delete -q gcr.io/${PROJECT_ID}/ci/hello-world:${BUILD_ID} - exit 0 # The previous step may not run if the build fails. Garbage collect any # images created by this script more than 4 weeks ago. This step should # not break the build on error, and it can start running as soon as the # build does. - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' + allowFailure: true waitFor: ['-'] entrypoint: 'bash' args: @@ -481,4 +444,3 @@ steps: xargs printf "gcr.io/${PROJECT_ID}/$${image}@$$1\n" done | \ xargs -P 4 -L 32 gcloud container images delete -q --force-delete-tags - exit 0 diff --git a/ci/cloudbuild/build.sh b/ci/cloudbuild/build.sh index a00172d5..78836b16 100755 --- a/ci/cloudbuild/build.sh +++ b/ci/cloudbuild/build.sh @@ -265,6 +265,7 @@ if [[ -n "${CLOUD_FLAG}" ]]; then "--region=us-east1" ) io::run gcloud builds submit "${args[@]}" . + exit fi # Default to --docker mode since no other mode was specified. diff --git a/ci/cloudbuild/builds/check-api.sh b/ci/cloudbuild/builds/check-api.sh index 5ed8ba0a..ac0a0dde 100755 --- a/ci/cloudbuild/builds/check-api.sh +++ b/ci/cloudbuild/builds/check-api.sh @@ -62,15 +62,17 @@ io::log_h2 "Searching for API changes in functions_framework_cpp" actual_dump_file="functions_framework_cpp.actual.abi.dump" expected_dump_file="functions_framework_cpp.expected.abi.dump" expected_dump_path="${PROJECT_ROOT}/ci/abi-dumps/${expected_dump_file}.gz" +exit_status=0 if [[ -r "${expected_dump_path}" ]]; then zcat "${expected_dump_path}" >"cmake-out/${expected_dump_file}" report="cmake-out/compat_reports/functions_framework_cpp/expected_to_actual/src_compat_report.html" # We ignore all symbols in internal namespaces, because these are not part # of our public API. We do this by specifying a regex that matches against - # the mangled symbol names. For example, 8 is the number of characters in - # the string "internal", and it should again be followed by some other - # number indicating the length of the symbol within the "internal" - # namespace. See: https://en.wikipedia.org/wiki/Name_mangling + # the mangled symbol names. For example, 18 is the number of characters in + # the string "functions_internal", and it should again be followed by some + # other number indicating the length of the symbol within the + # `functions_internal` namespace. + # See: https://en.wikipedia.org/wiki/Name_mangling if ! abi-compliance-checker \ -skip-internal-symbols "(18functions_internal)\d" \ -report-path "${report}" \ @@ -80,10 +82,11 @@ if [[ -r "${expected_dump_path}" ]]; then io::log_red "ABI Compliance error: functions_framework_cpp" io::log "Report file: ${report}" w3m -dump "${report}" - exit 1 + exit_status=1 fi fi # Replaces the (old) expected dump file with the (new) actual one. gzip -n "cmake-out/${actual_dump_file}" mv -f "cmake-out/${actual_dump_file}.gz" "${expected_dump_path}" +exit ${exit_status} diff --git a/ci/cloudbuild/cloudbuild.yaml b/ci/cloudbuild/cloudbuild.yaml index 7932a9c6..d63250a1 100644 --- a/ci/cloudbuild/cloudbuild.yaml +++ b/ci/cloudbuild/cloudbuild.yaml @@ -45,7 +45,7 @@ substitutions: _LOGS_BUCKET: 'cloud-cpp-community-publiclogs' steps: - - name: 'gcr.io/kaniko-project/executor:v1.6.0-debug' + - name: 'gcr.io/kaniko-project/executor:v1.12.0' args: [ '--log-format=text', '--context=dir:///workspace/ci', @@ -62,30 +62,26 @@ steps: - name: 'gcr.io/${PROJECT_ID}/${_IMAGE}:${BUILD_ID}' entrypoint: '/bin/true' - # Restores the homedir cache into /h in parallel with the previous step. - # Won't break the build if this step fails. - - name: 'gcr.io/cloud-builders/gsutil' - waitFor: [ '-' ] - entrypoint: 'bash' - dir: '/h' - args: - - '-c' - - > - /workspace/ci/cloudbuild/cache.sh restore - --bucket_url=gs://${_CACHE_BUCKET}/build-cache/functions-framework-cpp - --key=${_TRIGGER_SOURCE}/${_DISTRO}-${_BUILD_NAME}/h - --fallback_key=main/${_DISTRO}-${_BUILD_NAME}/h - || true + # Runs the specified build in the image that was created in the first step. + - name: 'gcr.io/${PROJECT_ID}/${_IMAGE}:${BUILD_ID}' + entrypoint: 'ci/cloudbuild/build.sh' + args: [ '--local', '--build', '${_BUILD_NAME}' ] + env: [ + 'SCCACHE_GCS_BUCKET=${_CACHE_BUCKET}', + 'SCCACHE_GCS_KEY_PREFIX=sccache/${_DISTRO}-${_BUILD_NAME}', + 'SCCACHE_GCS_RW_MODE=READ_WRITE', + 'VCPKG_BINARY_SOURCES=x-gcs,gs://${_CACHE_BUCKET}/vcpkg-cache/${_DISTRO}-${_BUILD_NAME},readwrite' + ] # Remove the images created by this build. - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' + allowFailure: true entrypoint: 'bash' args: - '-c' - | set +e gcloud container images delete -q gcr.io/${PROJECT_ID}/${_IMAGE}:${BUILD_ID} - exit 0 # The previous step may not run if the build fails. Garbage collect any # images created by this script, and/or similar scripts in this repository. @@ -95,6 +91,7 @@ steps: # can start running as soon as the build does. - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' waitFor: [ '-' ] + allowFailure: true entrypoint: 'bash' args: - '-c' @@ -104,4 +101,3 @@ steps: --format='get(digest)' --filter='timestamp.datetime < -P4W' | \ xargs -r printf "gcr.io/${PROJECT_ID}/${_IMAGE}@%s\n" | \ xargs -r -P 4 -L 32 gcloud container images delete -q --force-delete-tags - exit 0 diff --git a/ci/cloudbuild/dockerfiles/fedora-34.Dockerfile b/ci/cloudbuild/dockerfiles/fedora-34.Dockerfile index 2ad62897..4ef17d42 100644 --- a/ci/cloudbuild/dockerfiles/fedora-34.Dockerfile +++ b/ci/cloudbuild/dockerfiles/fedora-34.Dockerfile @@ -41,15 +41,16 @@ RUN curl -sSL https://github.com/universal-ctags/ctags/archive/refs/tags/p5.9.20 # https://github.com/lvc/abi-dumper/pull/29. We can switch back to `dnf install # abi-dumper` once it has the fix. WORKDIR /var/tmp/build -RUN curl -sSL https://github.com/lvc/abi-dumper/archive/814effec0f20a9613441dfa033aa0a0bc2a96a87.tar.gz | \ +RUN curl -sSL https://github.com/lvc/abi-dumper/archive/16bb467cd7d343dd3a16782b151b56cf15509594.tar.gz | \ tar -xzf - --strip-components=1 && \ mv abi-dumper.pl /usr/local/bin/abi-dumper && \ chmod +x /usr/local/bin/abi-dumper WORKDIR /var/tmp/gcloud -ARG GOOGLE_CLOUD_CPP_CLOUD_SDK_VERSION="348.0.0" -ARG GOOGLE_CLOUD_CPP_SDK_SHA256="8341a9b21088fd382522be247c7e51c61d8ea4ff86e6ededfa601afd5223e153" -ENV TARBALL="google-cloud-sdk-${GOOGLE_CLOUD_CPP_CLOUD_SDK_VERSION}-linux-x86_64.tar.gz" -RUN curl -sSL "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/${TARBALL}" -o "${TARBALL}" +ARG GOOGLE_CLOUD_CPP_CLOUD_SDK_VERSION="428.0.0" +ARG GOOGLE_CLOUD_CPP_SDK_SHA256="a665909d2ff9cd3a927d84670c5a8d11f0c5fbcda2540bbea44e0d6f77b82e27" +ENV TARBALL="google-cloud-cli-${GOOGLE_CLOUD_CPP_CLOUD_SDK_VERSION}-linux-x86_64.tar.gz" +RUN curl -fsSL "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/${TARBALL}" -o "${TARBALL}" RUN echo "${GOOGLE_CLOUD_CPP_SDK_SHA256} ${TARBALL}" | sha256sum --check - RUN tar x -C /usr/local -f "${TARBALL}" +ENV PATH=${PATH}:/usr/local/google-cloud-sdk/bin diff --git a/ci/cloudbuild/triggers/check-api-ci.yaml b/ci/cloudbuild/triggers/check-api-ci.yaml index bd990ed2..ef56772b 100644 --- a/ci/cloudbuild/triggers/check-api-ci.yaml +++ b/ci/cloudbuild/triggers/check-api-ci.yaml @@ -9,5 +9,6 @@ substitutions: _BUILD_NAME: check-api _DISTRO: fedora-34 _TRIGGER_TYPE: ci +includeBuildLogs: INCLUDE_BUILD_LOGS_WITH_STATUS tags: - ci diff --git a/ci/cloudbuild/triggers/check-api-pr.yaml b/ci/cloudbuild/triggers/check-api-pr.yaml index f234fcee..1b337f98 100644 --- a/ci/cloudbuild/triggers/check-api-pr.yaml +++ b/ci/cloudbuild/triggers/check-api-pr.yaml @@ -10,5 +10,6 @@ substitutions: _BUILD_NAME: check-api _DISTRO: fedora-34 _TRIGGER_TYPE: pr +includeBuildLogs: INCLUDE_BUILD_LOGS_WITH_STATUS tags: - pr diff --git a/ci/cloudbuild/triggers/pr.yaml b/ci/cloudbuild/triggers/pr.yaml new file mode 100644 index 00000000..a11e45fc --- /dev/null +++ b/ci/cloudbuild/triggers/pr.yaml @@ -0,0 +1,12 @@ +createTime: '2021-01-12T17:18:16.451278761Z' +description: Pull Request to functions-framework-cpp +filename: ci/build-examples.yaml +github: + name: functions-framework-cpp + owner: GoogleCloudPlatform + pullRequest: + branch: ^main$ + commentControl: COMMENTS_ENABLED_FOR_EXTERNAL_CONTRIBUTORS_ONLY +id: 18ddf837-91e7-47c7-850e-d52d1e701d17 +includeBuildLogs: INCLUDE_BUILD_LOGS_WITH_STATUS +name: pr diff --git a/ci/cloudbuild/triggers/push.yaml b/ci/cloudbuild/triggers/push.yaml new file mode 100644 index 00000000..2df019f6 --- /dev/null +++ b/ci/cloudbuild/triggers/push.yaml @@ -0,0 +1,11 @@ +createTime: '2021-01-12T17:19:25.822590886Z' +description: Push to branch +filename: ci/build-examples.yaml +github: + name: functions-framework-cpp + owner: GoogleCloudPlatform + push: + branch: ^main$ +id: c609a1b8-8acc-45ed-8aeb-c1ae7b070911 +includeBuildLogs: INCLUDE_BUILD_LOGS_WITH_STATUS +name: push diff --git a/ci/generate-build-examples.sh b/ci/generate-build-examples.sh index 920e0fcc..38dda597 100755 --- a/ci/generate-build-examples.sh +++ b/ci/generate-build-examples.sh @@ -32,7 +32,7 @@ steps: args: ['build', '-t', 'pack', '-f', 'build_scripts/pack.Dockerfile', 'build_scripts'] # Create the docker images for the buildpacks builder. - - name: 'gcr.io/kaniko-project/executor:v1.6.0-debug' + - name: 'gcr.io/kaniko-project/executor:v1.12.0' args: [ "--context=dir:///workspace/", "--dockerfile=build_scripts/Dockerfile", @@ -46,7 +46,7 @@ steps: - name: 'gcr.io/cloud-builders/docker' args: ['pull', 'gcr.io/${PROJECT_ID}/ci/run-image:${BUILD_ID}'] - - name: 'gcr.io/kaniko-project/executor:v1.6.0-debug' + - name: 'gcr.io/kaniko-project/executor:v1.12.0' args: [ "--context=dir:///workspace/", "--dockerfile=build_scripts/Dockerfile", @@ -92,7 +92,11 @@ generic_example() { waitFor: ['gcf-builder-ready'] id: '${container}' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=${signature}', +_EOF_ + if [[ "${signature}" != "declarative" ]] && [[ "${signature}" != "" ]]; then + echo " '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=${signature}'," + fi + cat <<_EOF_ '--env', 'GOOGLE_FUNCTION_TARGET=${function}', '--path', 'examples/${example}', '${container}', @@ -109,13 +113,21 @@ site_example() { if grep -E -q 'gcf::CloudEvent|google::cloud::functions::CloudEvent' ${example}/*; then signature="cloudevent" fi + if grep -E -q 'gcf::Function|google::cloud::functions::Function' ${example}/*; then + signature="declarative" + fi local container="site-${function}" + cat <<_EOF_ - name: 'pack' waitFor: ['gcf-builder-ready'] id: '${container}' args: ['build', - '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=${signature}', +_EOF_ + if [[ "${signature}" != "declarative" ]] && [[ "${signature}" != "" ]]; then + echo " '--env', 'GOOGLE_FUNCTION_SIGNATURE_TYPE=${signature}'," + fi + cat <<_EOF_ '--env', 'GOOGLE_FUNCTION_TARGET=${function}', '--path', '${example}', '${container}', @@ -123,16 +135,16 @@ site_example() { _EOF_ } -generic_example hello_cloud_event HelloCloudEvent cloudevent -generic_example hello_from_namespace hello_from_namespace::HelloWorld http -generic_example hello_from_namespace ::hello_from_namespace::HelloWorld http hello-from-namespace-rooted -generic_example hello_from_nested_namespace hello_from_nested_namespace::ns0::ns1::HelloWorld http -generic_example hello_multiple_sources HelloMultipleSources http -generic_example hello_gcs HelloGcs http -generic_example hello_with_third_party HelloWithThirdParty http -generic_example hello_world HelloWorld http -generic_example hello_world ::HelloWorld http hello-world-rooted -generic_example howto_use_legacy_code HowtoUseLegacyCode http howto-use-legacy-code +generic_example hello_cloud_event HelloCloudEvent declarative +generic_example hello_from_namespace hello_from_namespace::HelloWorld declarative +generic_example hello_from_namespace ::hello_from_namespace::HelloWorld declarative hello-from-namespace-rooted +generic_example hello_from_nested_namespace hello_from_nested_namespace::ns0::ns1::HelloWorld declarative +generic_example hello_multiple_sources HelloMultipleSources declarative +generic_example hello_gcs HelloGcs declarative +generic_example hello_with_third_party HelloWithThirdParty declarative +generic_example hello_world HelloWorld declarative +generic_example hello_world ::HelloWorld declarative hello-world-rooted +generic_example howto_use_legacy_code HowtoUseLegacyCode declarative howto-use-legacy-code cat <<_EOF_ # Build the cloud site examples. @@ -200,6 +212,7 @@ cat <<_EOF_ # Remove the images created by this build. - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' + allowFailure: true entrypoint: 'bash' args: - '-c' @@ -208,13 +221,13 @@ cat <<_EOF_ gcloud container images delete -q gcr.io/\${PROJECT_ID}/ci/run-image:\${BUILD_ID} gcloud container images delete -q gcr.io/\${PROJECT_ID}/ci/build-image:\${BUILD_ID} gcloud container images delete -q gcr.io/\${PROJECT_ID}/ci/hello-world:\${BUILD_ID} - exit 0 # The previous step may not run if the build fails. Garbage collect any # images created by this script more than 4 weeks ago. This step should # not break the build on error, and it can start running as soon as the # build does. - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' + allowFailure: true waitFor: ['-'] entrypoint: 'bash' args: @@ -227,5 +240,4 @@ cat <<_EOF_ xargs printf "gcr.io/\${PROJECT_ID}/\$\${image}@\$\$1\n" done | \\ xargs -P 4 -L 32 gcloud container images delete -q --force-delete-tags - exit 0 _EOF_ diff --git a/ci/pack/buildpack/bin/build b/ci/pack/buildpack/bin/build index 55babeba..61cb5103 100755 --- a/ci/pack/buildpack/bin/build +++ b/ci/pack/buildpack/bin/build @@ -130,11 +130,8 @@ generate_http_main_no_namespace() { local function="${1}" cat <<_EOF_ #include - namespace gcf = ::google::cloud::functions; - extern gcf::HttpResponse ${function}(gcf::HttpRequest); - int main(int argc, char* argv[]) { return gcf::Run(argc, argv, gcf::UserHttpFunction(${function})); } @@ -147,13 +144,10 @@ generate_http_main_with_namespace() { cat <<_EOF_ #include - namespace gcf = ::google::cloud::functions; - namespace ${namespace} { extern gcf::HttpResponse ${function}(gcf::HttpRequest); } // namespace - int main(int argc, char* argv[]) { return gcf::Run(argc, argv, gcf::UserHttpFunction(::${namespace}::${function})); } @@ -164,11 +158,8 @@ generate_cloud_event_main_no_namespace() { local function="${1}" cat <<_EOF_ #include - namespace gcf = ::google::cloud::functions; - extern void ${function}(gcf::CloudEvent); - int main(int argc, char* argv[]) { return gcf::Run(argc, argv, gcf::UserCloudEventFunction(${function})); } @@ -181,15 +172,43 @@ generate_cloud_event_main_with_namespace() { cat <<_EOF_ #include +namespace gcf = ::google::cloud::functions; +namespace ${namespace} { + extern void ${function}(gcf::CloudEvent); +} // namespace +int main(int argc, char* argv[]) { + return gcf::Run(argc, argv, gcf::UserCloudEventFunction(::${namespace}::${function})); +} +_EOF_ +} +generate_declarative_main_no_namespace() { + local function="${1}" + cat <<_EOF_ +#include +#include namespace gcf = ::google::cloud::functions; +extern gcf::Function ${function}(); +int main(int argc, char* argv[]) { + return gcf::Run(argc, argv, ${function}()); +} +_EOF_ +} +generate_declarative_main_with_namespace() { + local function="${1}" + local namespace="${2}" + + cat <<_EOF_ +#include +#include +namespace gcf = ::google::cloud::functions; namespace ${namespace} { - extern void ${function}(gcf::CloudEvent); + extern gcf::Function ${function}(); } // namespace int main(int argc, char* argv[]) { - return gcf::Run(argc, argv, gcf::UserCloudEventFunction(::${namespace}::${function})); + return gcf::Run(argc, argv, ::${namespace}::${function}()); } _EOF_ } @@ -224,12 +243,21 @@ generate_main() { return fi + if [[ "${signature}" == "declarative" ]] || [[ "${signature}" == "" ]]; then + if [[ -z "${namespace}" || "${namespace}" == "." ]]; then + generate_declarative_main_no_namespace "${function}" + return + fi + generate_declarative_main_with_namespace "${function}" "${namespace}" + return + fi + >&2 echo "Unknown function signature type: ${signature}" exit 1 } generate_main \ - "${GOOGLE_FUNCTION_TARGET}" "${GOOGLE_FUNCTION_SIGNATURE_TYPE:-http}" >"${layers}/source/main.cc" + "${GOOGLE_FUNCTION_TARGET}" "${GOOGLE_FUNCTION_SIGNATURE_TYPE:-}" >"${layers}/source/main.cc" echo "-----> Configure Function" cat >"${layers}/binary.toml" <<_EOF_ diff --git a/ci/update-markdown-code-snippets.sh b/ci/update-markdown-code-snippets.sh new file mode 100755 index 00000000..276d1a12 --- /dev/null +++ b/ci/update-markdown-code-snippets.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Copyright 2022 Google LLC +# +# 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. + +set -euo pipefail + +declare -A files=( + ["README.md"]="examples/hello_world/hello_world.cc" + ["examples/site/howto_create_container/README.md"]="examples/site/hello_world_http/hello_world_http.cc" + ["examples/site/howto_deploy_cloud_event/README.md"]="examples/site/hello_world_pubsub/hello_world_pubsub.cc" + ["examples/site/howto_deploy_to_cloud_run/README.md"]="examples/site/hello_world_http/hello_world_http.cc" + ["examples/site/testing_pubsub/README.md"]="examples/site/hello_world_pubsub/hello_world_pubsub.cc" + ["examples/site/testing_storage/README.md"]="examples/site/hello_world_storage/hello_world_storage.cc" +) + +for markdown in "${!files[@]}"; do + source="${files[$markdown]}" + ( + sed '//q' "${markdown}" + echo "[snippet source]: /${source}" + echo '```cc' + # Dumps the contents of the source file starting at the first #include, so we + # skip the license header comment. + sed -n -e '/END .*quickstart/,$d' -e '/^#/,$p' "${source}" | sed -e '/^\/\//d' -e 's; // NOLINT;;' + echo '```' + sed -n '//,$p' "${markdown}" + ) | sponge "${markdown}" +done diff --git a/examples/README.md b/examples/README.md index d72f4c7b..c3ad8448 100644 --- a/examples/README.md +++ b/examples/README.md @@ -16,14 +16,13 @@ Functions Framework for C++. The main audience for these notes are developers ## Create the Development and Runtime Docker Images These notes assume the reader is familiar with GCP, the Google Cloud SDK -command-line tool, and with the `docker(1)` -command-line tool. +command-line tool, and with the `docker(1)` command-line tool. To compile the examples you will need a Docker image with the development tools and core dependencies pre-compiled. To create this image run this command: ```sh -docker build -t ci-build-image --target gcf-cpp-develop -f build_scripts/Dockerfile . +docker build -t ci-build-image --target gcf-cpp-ci -f build_scripts/Dockerfile . ``` The runtime image is contains just the minimal components to execute a program diff --git a/examples/cloud_event_examples_test.cc b/examples/cloud_event_examples_test.cc index 7b4729f5..e47c3539 100644 --- a/examples/cloud_event_examples_test.cc +++ b/examples/cloud_event_examples_test.cc @@ -12,37 +12,48 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "google/cloud/functions/internal/parse_cloud_event_json.h" -#include "google/cloud/functions/cloud_event.h" +#include "google/cloud/functions/internal/function_impl.h" +#include "google/cloud/functions/function.h" #include namespace gcf = ::google::cloud::functions; +namespace gcf_internal = ::google::cloud::functions_internal; -void HelloCloudEvent(gcf::CloudEvent event); +gcf::Function HelloCloudEvent(); namespace { -TEST(HttpExamplesTest, HelloCloudEvent) { - EXPECT_NO_THROW(HelloCloudEvent( - google::cloud::functions_internal::ParseCloudEventJson(R"js({ - "specversion": "1.0", - "type": "google.cloud.pubsub.topic.v1.messagePublished", - "source": "//pubsub.googleapis.com/projects/sample-project/topics/gcf-test", - "id": "aaaaaa-1111-bbbb-2222-cccccccccccc", - "subject": "test-only", - "time": "2020-09-29T11:32:00.000Z", - "datacontenttype": "application/json", - "data": { - "subscription": "projects/sample-project/subscriptions/sample-subscription", - "message": { - "@type": "type.googleapis.com/google.pubsub.v1.PubsubMessage", - "attributes": { - "attr1":"attr1-value" - }, - "data": "" - } +auto constexpr kBody = R"js({ + "specversion": "1.0", + "type": "google.cloud.pubsub.topic.v1.messagePublished", + "source": "//pubsub.googleapis.com/projects/sample-project/topics/gcf-test", + "id": "aaaaaa-1111-bbbb-2222-cccccccccccc", + "subject": "test-only", + "time": "2020-09-29T11:32:00.000Z", + "datacontenttype": "application/json", + "data": { + "subscription": "projects/sample-project/subscriptions/sample-subscription", + "message": { + "@type": "type.googleapis.com/google.pubsub.v1.PubsubMessage", + "attributes": { + "attr1":"attr1-value" + }, + "data": "" } - })js"))); + } +})js"; + +TEST(HttpExamplesTest, HelloCloudEvent) { + auto function = HelloCloudEvent(); + gcf_internal::BeastRequest request; + request.insert("ce-type", "com.example.someevent"); + request.insert("ce-source", "/mycontext"); + request.insert("ce-id", "A234-1234-1234"); + request.body() = kBody; + auto handler = + gcf_internal::FunctionImpl::GetImpl(function)->GetHandler("unused"); + auto const response = handler(std::move(request)); + EXPECT_EQ(response.result_int(), 200); } } // namespace diff --git a/examples/hello_cloud_event/hello_cloud_event.cc b/examples/hello_cloud_event/hello_cloud_event.cc index 0e1141d0..648debae 100644 --- a/examples/hello_cloud_event/hello_cloud_event.cc +++ b/examples/hello_cloud_event/hello_cloud_event.cc @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include -using ::google::cloud::functions::CloudEvent; +namespace gcf = ::google::cloud::functions; -// Though not used in this function, `event` is passed by value to support -// applications that move-out its data. -void HelloCloudEvent(CloudEvent event) { // NOLINT - std::cout << "Received event" - << "\n id: " << event.id() - << "\n subject: " << event.subject().value_or("") << std::endl; +gcf::Function HelloCloudEvent() { + return gcf::MakeFunction([](gcf::CloudEvent const& event) { + std::cout << "Received event" + << "\n id: " << event.id() + << "\n subject: " << event.subject().value_or("") << std::endl; + }); } diff --git a/examples/hello_from_namespace/hello_from_namespace.cc b/examples/hello_from_namespace/hello_from_namespace.cc index f51831ae..6852e967 100644 --- a/examples/hello_from_namespace/hello_from_namespace.cc +++ b/examples/hello_from_namespace/hello_from_namespace.cc @@ -12,20 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include namespace hello_from_namespace { -using ::google::cloud::functions::HttpRequest; -using ::google::cloud::functions::HttpResponse; +namespace gcf = ::google::cloud::functions; -// Though not used in this example, the request is passed by value to support -// applications that move-out its data. -HttpResponse HelloWorld(HttpRequest) { // NOLINT - return HttpResponse{} - .set_header("Content-Type", "text/plain") - .set_payload("Hello from a C++ namespace!\n"); +gcf::Function HelloWorld() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + return gcf::HttpResponse{} + .set_header("Content-Type", "text/plain") + .set_payload("Hello from a C++ namespace!\n"); + }); } } // namespace hello_from_namespace diff --git a/examples/hello_from_nested_namespace/hello_from_nested_namespace.cc b/examples/hello_from_nested_namespace/hello_from_nested_namespace.cc index 28ac80f8..d67ec52a 100644 --- a/examples/hello_from_nested_namespace/hello_from_nested_namespace.cc +++ b/examples/hello_from_nested_namespace/hello_from_nested_namespace.cc @@ -12,20 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include namespace hello_from_nested_namespace::ns0::ns1 { -using ::google::cloud::functions::HttpRequest; -using ::google::cloud::functions::HttpResponse; +namespace gcf = ::google::cloud::functions; -// Though not used in this example, the request is passed by value to support -// applications that move-out its data. -HttpResponse HelloWorld(HttpRequest) { // NOLINT - return HttpResponse{} - .set_header("Content-Type", "text/plain") - .set_payload("Hello from a nested C++ namespace!\n"); +gcf::Function HelloWorld() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + return gcf::HttpResponse{} + .set_header("Content-Type", "text/plain") + .set_payload("Hello from a nested C++ namespace!\n"); + }); } } // namespace hello_from_nested_namespace::ns0::ns1 diff --git a/examples/hello_gcs/hello_gcs.cc b/examples/hello_gcs/hello_gcs.cc index 27add042..3d1a2052 100644 --- a/examples/hello_gcs/hello_gcs.cc +++ b/examples/hello_gcs/hello_gcs.cc @@ -12,37 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include #include #include -using ::google::cloud::functions::HttpRequest; -using ::google::cloud::functions::HttpResponse; +namespace gcf = ::google::cloud::functions; namespace gcs = ::google::cloud::storage; -// Though not used in this example, the request is passed by value to support -// applications that move-out its data. -HttpResponse HelloGcs(HttpRequest request) { // NOLINT - auto error = [] { - return HttpResponse{}.set_result(HttpResponse::kBadRequest); - }; +gcf::Function HelloGcs() { + return gcf::MakeFunction([](gcf::HttpRequest const& request) { + auto error = [] { + return gcf::HttpResponse{}.set_result(gcf::HttpResponse::kBadRequest); + }; - std::vector components; - std::istringstream split(request.target()); - for (std::string c; std::getline(split, c, '/'); components.push_back(c)) { - } + std::vector components; + std::istringstream split(request.target()); + for (std::string c; std::getline(split, c, '/'); components.push_back(c)) { + } - if (components.size() != 2) return error(); - auto const bucket = components[0]; - auto const object = components[1]; - auto client = gcs::Client::CreateDefaultClient().value(); - auto reader = client.ReadObject(bucket, object); - std::string contents(std::istreambuf_iterator{reader}, - std::istreambuf_iterator{}); - if (!reader.status().ok()) return error(); + if (components.size() != 2) return error(); + auto const bucket = components[0]; + auto const object = components[1]; + auto client = gcs::Client::CreateDefaultClient().value(); + auto reader = client.ReadObject(bucket, object); + std::string contents(std::istreambuf_iterator{reader}, + std::istreambuf_iterator{}); + if (!reader.status().ok()) return error(); - return HttpResponse{} - .set_header("Content-Type", "application/octet-stream") - .set_payload(std::move(contents)); + return gcf::HttpResponse{} + .set_header("Content-Type", "application/octet-stream") + .set_payload(std::move(contents)); + }); } diff --git a/examples/hello_multiple_sources/hello_multiple_sources.cc b/examples/hello_multiple_sources/hello_multiple_sources.cc index 99bc70b1..5d394df4 100644 --- a/examples/hello_multiple_sources/hello_multiple_sources.cc +++ b/examples/hello_multiple_sources/hello_multiple_sources.cc @@ -13,16 +13,14 @@ // limitations under the License. #include "greeting.h" -#include -#include +#include -using ::google::cloud::functions::HttpRequest; -using ::google::cloud::functions::HttpResponse; +namespace gcf = ::google::cloud::functions; -// Though not used in this example, the request is passed by value to support -// applications that move-out its data. -HttpResponse HelloMultipleSources(HttpRequest) { // NOLINT - return HttpResponse{} - .set_header("Content-Type", "text/plain") - .set_payload(Greeting()); +gcf::Function HelloMultipleSources() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + return gcf::HttpResponse{} + .set_header("Content-Type", "text/plain") + .set_payload(Greeting()); + }); } diff --git a/examples/hello_with_third_party/hello_with_third_party.cc b/examples/hello_with_third_party/hello_with_third_party.cc index 3907e1a9..975520b2 100644 --- a/examples/hello_with_third_party/hello_with_third_party.cc +++ b/examples/hello_with_third_party/hello_with_third_party.cc @@ -12,17 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include #include -using ::google::cloud::functions::HttpRequest; -using ::google::cloud::functions::HttpResponse; +namespace gcf = ::google::cloud::functions; -// Though not used in this example, the request is passed by value to support -// applications that move-out its data. -HttpResponse HelloWithThirdParty(HttpRequest request) { // NOLINT - return HttpResponse{} - .set_header("Content-Type", "text/plain") - .set_payload(fmt::format("Hello at {}\n", request.target())); +gcf::Function HelloWithThirdParty() { + return gcf::MakeFunction([](gcf::HttpRequest const& request) { + return gcf::HttpResponse{} + .set_header("Content-Type", "text/plain") + .set_payload(fmt::format("Hello at {}\n", request.target())); + }); } diff --git a/examples/hello_world/hello_world.cc b/examples/hello_world/hello_world.cc index 4dbe0a5d..01ccaa81 100644 --- a/examples/hello_world/hello_world.cc +++ b/examples/hello_world/hello_world.cc @@ -12,16 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include -using ::google::cloud::functions::HttpRequest; -using ::google::cloud::functions::HttpResponse; +namespace gcf = ::google::cloud::functions; -// Though not used in this example, the request is passed by value to support -// applications that move-out its data. -HttpResponse HelloWorld(HttpRequest) { // NOLINT - return HttpResponse{} - .set_header("Content-Type", "text/plain") - .set_payload("Hello World\n"); +gcf::Function HelloWorld() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + return gcf::HttpResponse{} + .set_header("Content-Type", "text/plain") + .set_payload("Hello World\n"); + }); } diff --git a/examples/howto_use_legacy_code/README.md b/examples/howto_use_legacy_code/README.md index 3e53580b..5b3e7e11 100644 --- a/examples/howto_use_legacy_code/README.md +++ b/examples/howto_use_legacy_code/README.md @@ -86,7 +86,7 @@ Run the CMake configure step. If needed, this will download and build any dependencies for your function: ```shell -cmake -H. -B.build -DCMAKE_TOOLCHAIN_FILE=$HOME/vcpkg/scripts/buildsystems/vcpkg.cmake +cmake -S . -B .build -DCMAKE_TOOLCHAIN_FILE=$HOME/vcpkg/scripts/buildsystems/vcpkg.cmake ``` You should see output like this: @@ -130,7 +130,7 @@ create a binary called `local_server` in the .build subdirectory. This will produce a standalone HTTP server, which you can run locally using: -```shell +```shell .build/main --port 8080 ``` diff --git a/examples/howto_use_legacy_code/howto_use_legacy_code.cc b/examples/howto_use_legacy_code/howto_use_legacy_code.cc index 9ff6e155..a4cd3df5 100644 --- a/examples/howto_use_legacy_code/howto_use_legacy_code.cc +++ b/examples/howto_use_legacy_code/howto_use_legacy_code.cc @@ -13,16 +13,14 @@ // limitations under the License. #include "legacy/legacy.h" -#include -#include +#include -using ::google::cloud::functions::HttpRequest; -using ::google::cloud::functions::HttpResponse; +namespace gcf = ::google::cloud::functions; -// Though not used in this example, the request is passed by value to support -// applications that move-out its data. -HttpResponse HowtoUseLegacyCode(HttpRequest) { // NOLINT - return HttpResponse{} - .set_header("Content-Type", "text/plain") - .set_payload(legacy::LegacyMessage()); +gcf::Function HowtoUseLegacyCode() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + return gcf::HttpResponse{} + .set_header("Content-Type", "text/plain") + .set_payload(legacy::LegacyMessage()); + }); } diff --git a/examples/howto_use_legacy_code/main.cc b/examples/howto_use_legacy_code/main.cc index 28197c0d..7e584ff5 100644 --- a/examples/howto_use_legacy_code/main.cc +++ b/examples/howto_use_legacy_code/main.cc @@ -17,8 +17,8 @@ namespace gcf = ::google::cloud::functions; -gcf::HttpResponse HowtoUseLegacyCode(gcf::HttpRequest); +gcf::Function HowtoUseLegacyCode(); int main(int argc, char* argv[]) { - return ::google::cloud::functions::Run(argc, argv, HowtoUseLegacyCode); + return ::google::cloud::functions::Run(argc, argv, HowtoUseLegacyCode()); } diff --git a/examples/http_examples_test.cc b/examples/http_examples_test.cc index 38e37d89..e17155ac 100644 --- a/examples/http_examples_test.cc +++ b/examples/http_examples_test.cc @@ -12,65 +12,76 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include -#include +#include #include namespace gcf = ::google::cloud::functions; +namespace gcf_internal = ::google::cloud::functions_internal; -gcf::HttpResponse HelloGcs(gcf::HttpRequest); -gcf::HttpResponse HelloMultipleSources(gcf::HttpRequest); -gcf::HttpResponse HelloWithThirdParty(gcf::HttpRequest request); -gcf::HttpResponse HelloWorld(gcf::HttpRequest); -gcf::HttpResponse HowtoUseLegacyCode(gcf::HttpRequest); +gcf::Function HelloGcs(); +gcf::Function HelloMultipleSources(); +gcf::Function HelloWithThirdParty(); +gcf::Function HelloWorld(); +gcf::Function HowtoUseLegacyCode(); namespace hello_from_namespace { -gcf::HttpResponse HelloWorld(gcf::HttpRequest); +gcf::Function HelloWorld(); } // namespace hello_from_namespace namespace hello_from_nested_namespace::ns0::ns1 { -gcf::HttpResponse HelloWorld(gcf::HttpRequest); +gcf::Function HelloWorld(); } // namespace hello_from_nested_namespace::ns0::ns1 namespace { using ::testing::HasSubstr; +auto TriggerFunction(gcf::Function const& function, + std::optional target = std::nullopt) { + gcf_internal::BeastRequest request; + if (target) request.target(std::move(*target)); + + auto handler = + gcf_internal::FunctionImpl::GetImpl(function)->GetHandler("unused"); + return handler(std::move(request)); +} + TEST(HttpExamplesTest, HelloGcs) { - auto const actual = HelloGcs(gcf::HttpRequest{}.set_target("/")); - EXPECT_EQ(actual.result(), gcf::HttpResponse::kBadRequest); + auto const actual = TriggerFunction(HelloGcs(), "/"); + EXPECT_EQ(actual.result_int(), 400); } TEST(HttpExamplesTest, HelloMultipleSources) { - auto const actual = HelloMultipleSources(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("Hello World")); + auto const actual = TriggerFunction(HelloMultipleSources()); + EXPECT_THAT(actual.body(), HasSubstr("Hello World")); } TEST(HttpExamplesTest, HelloWithThirdParty) { - auto const actual = - HelloWithThirdParty(gcf::HttpRequest{}.set_target("/here")); - EXPECT_THAT(actual.payload(), HasSubstr("Hello at /here")); + auto const actual = TriggerFunction(HelloWithThirdParty(), "/here"); + EXPECT_THAT(actual.body(), HasSubstr("Hello at /here")); } TEST(HttpExamplesTest, HelloWorld) { - auto const actual = HelloWorld(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("Hello World")); + auto const actual = TriggerFunction(HelloWorld()); + EXPECT_THAT(actual.body(), HasSubstr("Hello World")); } TEST(HttpExamplesTest, HelloFromNamespace) { - auto const actual = hello_from_namespace::HelloWorld(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("C++ namespace")); + auto const actual = TriggerFunction(hello_from_namespace::HelloWorld()); + EXPECT_THAT(actual.body(), HasSubstr("C++ namespace")); } TEST(HttpExamplesTest, HelloFromNestedNamespace) { auto const actual = - hello_from_nested_namespace::ns0::ns1::HelloWorld(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("C++ namespace")); + TriggerFunction(hello_from_nested_namespace::ns0::ns1::HelloWorld()); + EXPECT_THAT(actual.body(), HasSubstr("C++ namespace")); } TEST(HttpExampleTest, HowtoUseLegacyCode) { - auto const actual = HowtoUseLegacyCode(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("C++")); + auto const actual = TriggerFunction(HowtoUseLegacyCode()); + EXPECT_THAT(actual.body(), HasSubstr("C++")); } } // namespace diff --git a/examples/site/bearer_token/bearer_token.cc b/examples/site/bearer_token/bearer_token.cc index fad8408b..8e2238d7 100644 --- a/examples/site/bearer_token/bearer_token.cc +++ b/examples/site/bearer_token/bearer_token.cc @@ -13,8 +13,7 @@ // limitations under the License. // [START functions_bearer_token] -#include -#include +#include #include #include #include @@ -31,7 +30,7 @@ gcf::HttpResponse HttpGet(std::string const& url, std::string const& authorization_header); } // namespace -gcf::HttpResponse bearer_token(gcf::HttpRequest request) { // NOLINT +gcf::HttpResponse bearer_token_impl(gcf::HttpRequest const& request) { static auto const kTargetUrl = [] { auto const* target_url = std::getenv("TARGET_URL"); if (target_url != nullptr) return std::string(target_url); @@ -61,6 +60,8 @@ gcf::HttpResponse bearer_token(gcf::HttpRequest request) { // NOLINT os << "Error creating authorization header: " << std::move(header).status(); throw std::runtime_error(std::move(os).str()); } + +gcf::Function bearer_token() { return gcf::MakeFunction(bearer_token_impl); } // [END functions_bearer_token] namespace { diff --git a/examples/site/concepts_after_response/concepts_after_response.cc b/examples/site/concepts_after_response/concepts_after_response.cc index cba26f87..7f2413a7 100644 --- a/examples/site/concepts_after_response/concepts_after_response.cc +++ b/examples/site/concepts_after_response/concepts_after_response.cc @@ -13,21 +13,21 @@ // limitations under the License. // [START functions_concepts_after_response] -#include -#include +#include #include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse concepts_after_response( - gcf::HttpRequest /*request*/) { // NOLINT - (void)std::async(std::launch::async, [] { - // This code may fail to complete, or even fail to start at all. - auto constexpr kIterations = 10; - int sum = 0; - for (int i = 0; i != kIterations; ++i) sum += i; - return sum; +gcf::Function concepts_after_response() { + return gcf::MakeFunction([](gcf::HttpRequest const&) { + (void)std::async(std::launch::async, [] { + // This code may fail to complete, or even fail to start at all. + auto constexpr kIterations = 10; + int sum = 0; + for (int i = 0; i != kIterations; ++i) sum += i; + return sum; + }); + return gcf::HttpResponse{}.set_payload("Hello World!"); }); - return gcf::HttpResponse{}.set_payload("Hello World!"); } // [END functions_concepts_after_response] diff --git a/examples/site/concepts_after_timeout/concepts_after_timeout.cc b/examples/site/concepts_after_timeout/concepts_after_timeout.cc index 67206a2a..a27c8dde 100644 --- a/examples/site/concepts_after_timeout/concepts_after_timeout.cc +++ b/examples/site/concepts_after_timeout/concepts_after_timeout.cc @@ -13,19 +13,20 @@ // limitations under the License. // [START functions_concepts_after_timeout] -#include -#include +#include #include #include #include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse concepts_after_timeout(gcf::HttpRequest request) { // NOLINT +gcf::Function concepts_after_timeout() { using std::chrono::minutes; - std::cout << "Function running..." << std::endl; - if (request.verb() == "GET") std::this_thread::sleep_for(minutes(2)); - std::cout << "Function completed!" << std::endl; - return gcf::HttpResponse{}.set_payload("Function completed!"); + return gcf::MakeFunction([](gcf::HttpRequest const& request) { + std::cout << "Function running..." << std::endl; + if (request.verb() == "GET") std::this_thread::sleep_for(minutes(2)); + std::cout << "Function completed!" << std::endl; + return gcf::HttpResponse{}.set_payload("Function completed!"); + }); } // [END functions_concepts_after_timeout] diff --git a/examples/site/concepts_filesystem/concepts_filesystem.cc b/examples/site/concepts_filesystem/concepts_filesystem.cc index d1ef30e2..0a4a7c72 100644 --- a/examples/site/concepts_filesystem/concepts_filesystem.cc +++ b/examples/site/concepts_filesystem/concepts_filesystem.cc @@ -13,20 +13,21 @@ // limitations under the License. // [START functions_concepts_filesystem] -#include -#include +#include #include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse concepts_filesystem(gcf::HttpRequest /*request*/) { // NOLINT - std::string payload; - for (auto const& p : std::filesystem::directory_iterator(".")) { - payload += p.path().generic_string(); - payload += "\n"; - } - return gcf::HttpResponse{} - .set_header("content-type", "text/plain") - .set_payload(payload); +gcf::Function concepts_filesystem() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + std::string payload; + for (auto const& p : std::filesystem::directory_iterator(".")) { + payload += p.path().generic_string(); + payload += "\n"; + } + return gcf::HttpResponse{} + .set_header("content-type", "text/plain") + .set_payload(payload); + }); } // [END functions_concepts_filesystem] diff --git a/examples/site/concepts_request/concepts_request.cc b/examples/site/concepts_request/concepts_request.cc index 9c021689..a601d50b 100644 --- a/examples/site/concepts_request/concepts_request.cc +++ b/examples/site/concepts_request/concepts_request.cc @@ -13,8 +13,7 @@ // limitations under the License. // [START functions_concepts_requests] -#include -#include +#include #include namespace gcf = ::google::cloud::functions; @@ -24,11 +23,13 @@ namespace { unsigned int make_http_request(std::string const& host); } // namespace -gcf::HttpResponse concepts_request(gcf::HttpRequest /*request*/) { // NOLINT - std::string const host = "example.com"; - auto const code = make_http_request(host); - return gcf::HttpResponse{}.set_payload( - "Received code " + std::to_string(code) + " from " + host); +gcf::Function concepts_request() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + std::string const host = "example.com"; + auto const code = make_http_request(host); + return gcf::HttpResponse{}.set_payload( + "Received code " + std::to_string(code) + " from " + host); + }); } // [END functions_concepts_requests] diff --git a/examples/site/concepts_stateless/concepts_stateless.cc b/examples/site/concepts_stateless/concepts_stateless.cc index ad65e81d..9e3286a3 100644 --- a/examples/site/concepts_stateless/concepts_stateless.cc +++ b/examples/site/concepts_stateless/concepts_stateless.cc @@ -13,8 +13,7 @@ // limitations under the License. // [START functions_concepts_stateless] -#include -#include +#include #include #include @@ -24,8 +23,10 @@ namespace { std::atomic count{0}; } // namespace -gcf::HttpResponse concepts_stateless(gcf::HttpRequest /*request*/) { // NOLINT - return gcf::HttpResponse{}.set_payload("Instance execution count: " + - std::to_string(++count)); +gcf::Function concepts_stateless() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + return gcf::HttpResponse{}.set_payload("Instance execution count: " + + std::to_string(++count)); + }); } // [END functions_concepts_stateless] diff --git a/examples/site/env_vars/env_vars.cc b/examples/site/env_vars/env_vars.cc index 6f40bce7..571f7381 100644 --- a/examples/site/env_vars/env_vars.cc +++ b/examples/site/env_vars/env_vars.cc @@ -13,15 +13,16 @@ // limitations under the License. // [START functions_env_vars] -#include -#include +#include #include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse env_vars(gcf::HttpRequest /*request*/) { // NOLINT - char const* value = std::getenv("FOO"); - if (value == nullptr) value = "FOO environment variable is not set"; - return gcf::HttpResponse{}.set_payload(value); +gcf::Function env_vars() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + char const* value = std::getenv("FOO"); + if (value == nullptr) value = "FOO environment variable is not set"; + return gcf::HttpResponse{}.set_payload(value); + }); } // [END functions_env_vars] diff --git a/examples/site/hello_world_error/hello_world_error.cc b/examples/site/hello_world_error/hello_world_error.cc index 0bf7cb70..39838d4b 100644 --- a/examples/site/hello_world_error/hello_world_error.cc +++ b/examples/site/hello_world_error/hello_world_error.cc @@ -13,46 +13,45 @@ // limitations under the License. // [START functions_helloworld_error] -#include -#include +#include #include #include namespace gcf = ::google::cloud::functions; -// Though not used in this example, the request is passed by value to support -// applications that move-out its data. -gcf::HttpResponse hello_world_error(gcf::HttpRequest request) { // NOLINT - if (request.target() == "/return500") { - // An error response code does NOT create entries in Error Reporting - return gcf::HttpResponse{}.set_result( - gcf::HttpResponse::kInternalServerError); - } - // Unstructured logs to stdout and/or stderr do NOT create entries in Error - // Reporting - std::cout << "An error occurred (stdout)\n"; - std::cerr << "An error occurred (stderr)\n"; +gcf::Function hello_world_error() { + return gcf::MakeFunction([](gcf::HttpRequest const& request) { + if (request.target() == "/return500") { + // An error response code does NOT create entries in Error Reporting + return gcf::HttpResponse{}.set_result( + gcf::HttpResponse::kInternalServerError); + } + // Unstructured logs to stdout and/or stderr do NOT create entries in Error + // Reporting + std::cout << "An error occurred (stdout)\n"; + std::cerr << "An error occurred (stderr)\n"; - if (request.target() == "/throw/exception") { - // Throwing an exception WILL create new entries in Error Reporting - throw std::runtime_error("I failed you"); - } + if (request.target() == "/throw/exception") { + // Throwing an exception WILL create new entries in Error Reporting + throw std::runtime_error("I failed you"); + } - // Structured logs MAY create entries in Error Reporting depending on their - // severity. You can create structured logs manually (as shown here), or using - // your favorite logging library with suitable formatting. - std::cerr << nlohmann::json{{"severity", "info"}, - {"message", "informational message"}} - .dump() - << std::endl; + // Structured logs MAY create entries in Error Reporting depending on their + // severity. You can create structured logs manually (as shown here), or + // using your favorite logging library with suitable formatting. + std::cerr << nlohmann::json{{"severity", "info"}, + {"message", "informational message"}} + .dump() + << std::endl; - std::cerr << nlohmann::json{{"severity", "error"}, - {"message", "an error message"}} - .dump() - << std::endl; + std::cerr << nlohmann::json{{"severity", "error"}, + {"message", "an error message"}} + .dump() + << std::endl; - return gcf::HttpResponse{} - .set_header("content-type", "text/plain") - .set_payload("Hello World!"); + return gcf::HttpResponse{} + .set_header("content-type", "text/plain") + .set_payload("Hello World!"); + }); } // [END functions_helloworld_error] diff --git a/examples/site/hello_world_get/hello_world_get.cc b/examples/site/hello_world_get/hello_world_get.cc index 973c731e..ac508186 100644 --- a/examples/site/hello_world_get/hello_world_get.cc +++ b/examples/site/hello_world_get/hello_world_get.cc @@ -13,16 +13,15 @@ // limitations under the License. // [START functions_helloworld_get] -#include -#include +#include namespace gcf = ::google::cloud::functions; -// Though not used in this example, the request is passed by value to support -// applications that move-out its data. -gcf::HttpResponse hello_world_get(gcf::HttpRequest) { // NOLINT - return gcf::HttpResponse{} - .set_header("content-type", "text/plain") - .set_payload("Hello World!"); +gcf::Function hello_world_get() { + return gcf::MakeFunction([](gcf::HttpRequest const&) { + return gcf::HttpResponse{} + .set_header("content-type", "text/plain") + .set_payload("Hello World!"); + }); } // [END functions_helloworld_get] diff --git a/examples/site/hello_world_http/hello_world_http.cc b/examples/site/hello_world_http/hello_world_http.cc index 26ce9ca3..eae24855 100644 --- a/examples/site/hello_world_http/hello_world_http.cc +++ b/examples/site/hello_world_http/hello_world_http.cc @@ -13,13 +13,12 @@ // limitations under the License. // [START functions_helloworld_http] -#include -#include +#include #include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse hello_world_http(gcf::HttpRequest request) { +gcf::HttpResponse hello_world_http_impl(gcf::HttpRequest request) { auto greeting = [r = std::move(request)] { auto request_json = nlohmann::json::parse(r.payload(), /*cb=*/nullptr, /*allow_exceptions=*/false); @@ -33,4 +32,8 @@ gcf::HttpResponse hello_world_http(gcf::HttpRequest request) { .set_header("content-type", "text/plain") .set_payload(greeting()); } + +gcf::Function hello_world_http() { + return gcf::MakeFunction(hello_world_http_impl); +} // [END functions_helloworld_http] diff --git a/examples/site/hello_world_pubsub/hello_world_pubsub.cc b/examples/site/hello_world_pubsub/hello_world_pubsub.cc index 779ef27d..7eaf20c0 100644 --- a/examples/site/hello_world_pubsub/hello_world_pubsub.cc +++ b/examples/site/hello_world_pubsub/hello_world_pubsub.cc @@ -13,16 +13,14 @@ // limitations under the License. // [START functions_helloworld_pubsub] -#include +#include #include #include #include namespace gcf = ::google::cloud::functions; -// Though not used in this example, the event is passed by value to support -// applications that move-out its data. -void hello_world_pubsub(gcf::CloudEvent event) { // NOLINT +void hello_world_pubsub_impl(gcf::CloudEvent const& event) { if (event.data_content_type().value_or("") != "application/json") { BOOST_LOG_TRIVIAL(error) << "expected application/json data"; return; @@ -32,4 +30,8 @@ void hello_world_pubsub(gcf::CloudEvent event) { // NOLINT payload["message"]["data"].get()); BOOST_LOG_TRIVIAL(info) << "Hello " << (name.empty() ? "World" : name); } + +gcf::Function hello_world_pubsub() { + return gcf::MakeFunction(hello_world_pubsub_impl); +} // [END functions_helloworld_pubsub] diff --git a/examples/site/hello_world_storage/hello_world_storage.cc b/examples/site/hello_world_storage/hello_world_storage.cc index 4687288a..428bca64 100644 --- a/examples/site/hello_world_storage/hello_world_storage.cc +++ b/examples/site/hello_world_storage/hello_world_storage.cc @@ -13,15 +13,13 @@ // limitations under the License. // [START functions_helloworld_storage] -#include +#include #include #include namespace gcf = ::google::cloud::functions; -// Though not used in this example, the event is passed by value to support -// applications that move-out its data. -void hello_world_storage(gcf::CloudEvent event) { // NOLINT +void hello_world_storage_impl(gcf::CloudEvent const& event) { if (event.data_content_type().value_or("") != "application/json") { BOOST_LOG_TRIVIAL(error) << "expected application/json data"; return; @@ -36,4 +34,8 @@ void hello_world_storage(gcf::CloudEvent event) { // NOLINT BOOST_LOG_TRIVIAL(info) << "Created: " << payload.value("timeCreated", ""); BOOST_LOG_TRIVIAL(info) << "Updated: " << payload.value("updated", ""); } + +gcf::Function hello_world_storage() { + return gcf::MakeFunction(hello_world_storage_impl); +} // [END functions_helloworld_storage] diff --git a/examples/site/howto_create_container/README.md b/examples/site/howto_create_container/README.md index 9b03efcb..34e8818f 100644 --- a/examples/site/howto_create_container/README.md +++ b/examples/site/howto_create_container/README.md @@ -25,16 +25,17 @@ pack version # Output: a version number, e.g., 0.17.0+git-d9cb4e7.build-2045 ``` -In this guide we will be using the [HTTP hello word][hello-world-http] function: +In this guide we will be using this [function][snippet source]: + +[snippet source]: /examples/site/hello_world_http/hello_world_http.cc ```cc -#include -#include +#include #include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse hello_world_http(gcf::HttpRequest request) { +gcf::HttpResponse hello_world_http_impl(gcf::HttpRequest request) { auto greeting = [r = std::move(request)] { auto request_json = nlohmann::json::parse(r.payload(), /*cb=*/nullptr, /*allow_exceptions=*/false); @@ -48,7 +49,12 @@ gcf::HttpResponse hello_world_http(gcf::HttpRequest request) { .set_header("content-type", "text/plain") .set_payload(greeting()); } + +gcf::Function hello_world_http() { + return gcf::MakeFunction(hello_world_http_impl); +} ``` + ## Getting the code for this example @@ -81,8 +87,7 @@ containing your function: ```shell pack build \ - --builder gcr.io/buildpacks/builder:latest \ - --env GOOGLE_FUNCTION_SIGNATURE_TYPE=http \ + --builder gcr.io/buildpacks/builder:v1 \ --env GOOGLE_FUNCTION_TARGET=hello_world_http \ --path examples/site/hello_world_http \ gcf-cpp-hello-world-http @@ -126,5 +131,4 @@ docker image rm gcf-cpp-hello-world-http [docker-install]: https://store.docker.com/search?type=edition&offering=community [sudoless docker]: https://docs.docker.com/engine/install/linux-postinstall/ [pack-install]: https://buildpacks.io/docs/install-pack/ -[hello-world-http]: /examples/site/hello_world_http/hello_world_http.cc [Google Cloud buildpack]: https://github.com/GoogleCloudPlatform/buildpacks diff --git a/examples/site/howto_deploy_cloud_event/README.md b/examples/site/howto_deploy_cloud_event/README.md index 87560ce4..80c1b6c2 100644 --- a/examples/site/howto_deploy_cloud_event/README.md +++ b/examples/site/howto_deploy_cloud_event/README.md @@ -28,17 +28,19 @@ pack version # Output: a version number, e.g., 0.17.0+git-d9cb4e7.build-2045 ``` -In this guide we will be using the [Pub/Sub hello word][hello-world-pubsub] function: +In this guide we will be using this [function][snippet source]: + +[snippet source]: /examples/site/hello_world_pubsub/hello_world_pubsub.cc ```cc -#include +#include #include #include #include namespace gcf = ::google::cloud::functions; -void hello_world_pubsub(gcf::CloudEvent event) { +void hello_world_pubsub_impl(gcf::CloudEvent const& event) { if (event.data_content_type().value_or("") != "application/json") { BOOST_LOG_TRIVIAL(error) << "expected application/json data"; return; @@ -48,7 +50,12 @@ void hello_world_pubsub(gcf::CloudEvent event) { payload["message"]["data"].get()); BOOST_LOG_TRIVIAL(info) << "Hello " << (name.empty() ? "World" : name); } + +gcf::Function hello_world_pubsub() { + return gcf::MakeFunction(hello_world_pubsub_impl); +} ``` + ## Getting the code for this example @@ -83,8 +90,7 @@ containing your function: ```shell GOOGLE_CLOUD_PROJECT=... # put the right value here pack build \ - --builder gcr.io/buildpacks/builder:latest \ - --env GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent \ + --builder gcr.io/buildpacks/builder:v1 \ --env GOOGLE_FUNCTION_TARGET=hello_world_pubsub \ --path examples/site/hello_world_pubsub \ "gcr.io/${GOOGLE_CLOUD_PROJECT}/gcf-cpp-hello-world-pubsub" @@ -207,6 +213,5 @@ gcloud container images delete \ [docker-install]: https://store.docker.com/search?type=edition&offering=community [sudoless docker]: https://docs.docker.com/engine/install/linux-postinstall/ [pack-install]: https://buildpacks.io/docs/install-pack/ -[hello-world-pubsub]: /examples/site/hello_world_pubsub/hello_world_pubsub.cc [gcloud-eventarc-create]: https://cloud.google.com/sdk/gcloud/reference/beta/eventarc/triggers/create [Google Cloud buildpack]: https://github.com/GoogleCloudPlatform/buildpacks diff --git a/examples/site/howto_deploy_to_cloud_run/README.md b/examples/site/howto_deploy_to_cloud_run/README.md index 41f7160c..ec58f57d 100644 --- a/examples/site/howto_deploy_to_cloud_run/README.md +++ b/examples/site/howto_deploy_to_cloud_run/README.md @@ -9,7 +9,6 @@ [docker-install]: https://store.docker.com/search?type=edition&offering=community [sudoless docker]: https://docs.docker.com/engine/install/linux-postinstall/ [pack-install]: https://buildpacks.io/docs/install-pack/ -[hello-world-http]: /examples/site/hello_world_http/hello_world_http.cc ## Pre-requisites @@ -39,16 +38,17 @@ pack version # Output: a version number, e.g., 0.17.0+git-d9cb4e7.build-2045 ``` -In this guide we will be using the [HTTP hello word][hello-world-http] function: +In this guide we will be using this [function][snippet source]: + +[snippet source]: /examples/site/hello_world_http/hello_world_http.cc ```cc -#include -#include +#include #include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse hello_world_http(gcf::HttpRequest request) { +gcf::HttpResponse hello_world_http_impl(gcf::HttpRequest request) { auto greeting = [r = std::move(request)] { auto request_json = nlohmann::json::parse(r.payload(), /*cb=*/nullptr, /*allow_exceptions=*/false); @@ -62,7 +62,12 @@ gcf::HttpResponse hello_world_http(gcf::HttpRequest request) { .set_header("content-type", "text/plain") .set_payload(greeting()); } + +gcf::Function hello_world_http() { + return gcf::MakeFunction(hello_world_http_impl); +} ``` + ## Getting the code for this example @@ -97,8 +102,7 @@ containing your function: ```shell GOOGLE_CLOUD_PROJECT=... # put the right value here pack build \ - --builder gcr.io/buildpacks/builder:latest \ - --env GOOGLE_FUNCTION_SIGNATURE_TYPE=http \ + --builder gcr.io/buildpacks/builder:v1 \ --env GOOGLE_FUNCTION_TARGET=hello_world_http \ --path examples/site/hello_world_http \ "gcr.io/${GOOGLE_CLOUD_PROJECT}/gcf-cpp-hello-world-http" diff --git a/examples/site/howto_local_development/CMakeLists.txt b/examples/site/howto_local_development/CMakeLists.txt index 455e2e99..8cfdd3e5 100644 --- a/examples/site/howto_local_development/CMakeLists.txt +++ b/examples/site/howto_local_development/CMakeLists.txt @@ -18,7 +18,7 @@ # CMake-based projects. cmake_minimum_required(VERSION 3.5) -project(functions-framework-cpp-howto-local-development CXX C) +project(functions-framework-cpp-howto-local-development CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/examples/site/howto_local_development/README.md b/examples/site/howto_local_development/README.md index 2e8de3e3..2792bcaf 100644 --- a/examples/site/howto_local_development/README.md +++ b/examples/site/howto_local_development/README.md @@ -74,7 +74,7 @@ Run the CMake configure step. If needed, this will download and build any dependencies for your function: ```shell -cmake -H. -B.build -DCMAKE_TOOLCHAIN_FILE=$HOME/vcpkg/scripts/buildsystems/vcpkg.cmake +cmake -S . -B .build -DCMAKE_TOOLCHAIN_FILE=$HOME/vcpkg/scripts/buildsystems/vcpkg.cmake ``` You should see output like this: @@ -118,7 +118,7 @@ create a binary called `local_server` in the .build subdirectory. This will produce a standalone HTTP server, which you can run locally using: -```shell +```shell .build/local_server --port 8080 ``` diff --git a/examples/site/howto_local_development/local_server.cc b/examples/site/howto_local_development/local_server.cc index 9a511e17..0cd00951 100644 --- a/examples/site/howto_local_development/local_server.cc +++ b/examples/site/howto_local_development/local_server.cc @@ -19,14 +19,14 @@ namespace gcf = ::google::cloud::functions; namespace { -gcf::HttpResponse HelloWithShutdown(gcf::HttpRequest const& /*request*/) { - return gcf::HttpResponse{} - .set_header("Content-Type", "text/plain") - .set_payload("Hello World\n"); +gcf::Function HelloLocal() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + return gcf::HttpResponse{} + .set_header("Content-Type", "text/plain") + .set_payload("Hello World\n"); + }); } } // namespace -int main(int argc, char* argv[]) { - return ::google::cloud::functions::Run(argc, argv, HelloWithShutdown); -} +int main(int argc, char* argv[]) { return gcf::Run(argc, argv, HelloLocal()); } diff --git a/examples/site/http_content/http_content.cc b/examples/site/http_content/http_content.cc index 2e59fe00..e52b107a 100644 --- a/examples/site/http_content/http_content.cc +++ b/examples/site/http_content/http_content.cc @@ -13,8 +13,7 @@ // limitations under the License. // [START functions_http_content] -#include -#include +#include #include #include #include @@ -29,25 +28,27 @@ std::map parse_www_form_urlencoded( std::string const& text); } // namespace -gcf::HttpResponse http_content(gcf::HttpRequest request) { // NOLINT - std::string name; - auto const& headers = request.headers(); - if (auto f = headers.find("content-type"); f != headers.end()) { - if (f->second == "application/json") { - name = nlohmann::json::parse(request.payload()).value("name", ""); - } else if (f->second == "application/octet-stream" || - f->second == "text/plain") { - name = request.payload(); // treat contents as a string - } else if (f->second == "application/x-www-form-urlencoded") { - // Use your preferred parser, here we use some custom code. - auto form = parse_www_form_urlencoded(request.payload()); - name = form["name"]; +gcf::Function http_content() { + return gcf::MakeFunction([](gcf::HttpRequest const& request) { + std::string name; + auto const& headers = request.headers(); + if (auto f = headers.find("content-type"); f != headers.end()) { + if (f->second == "application/json") { + name = nlohmann::json::parse(request.payload()).value("name", ""); + } else if (f->second == "application/octet-stream" || + f->second == "text/plain") { + name = request.payload(); // treat contents as a string + } else if (f->second == "application/x-www-form-urlencoded") { + // Use your preferred parser, here we use some custom code. + auto form = parse_www_form_urlencoded(request.payload()); + name = form["name"]; + } } - } - return gcf::HttpResponse{} - .set_header("content-type", "text/plain") - .set_payload("Hello " + name); + return gcf::HttpResponse{} + .set_header("content-type", "text/plain") + .set_payload("Hello " + name); + }); } // [END functions_http_content] diff --git a/examples/site/http_cors/http_cors.cc b/examples/site/http_cors/http_cors.cc index ac998ced..46647dc9 100644 --- a/examples/site/http_cors/http_cors.cc +++ b/examples/site/http_cors/http_cors.cc @@ -13,27 +13,28 @@ // limitations under the License. // [START functions_http_cors] -#include -#include +#include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse http_cors(gcf::HttpRequest request) { // NOLINT - // Set CORS headers for preflight request - if (request.verb() == "OPTIONS") { - // Allows GET requests from any origin with the Content-Type header and - // caches preflight response for an 3600s +gcf::Function http_cors() { + return gcf::MakeFunction([](gcf::HttpRequest const& request) { + // Set CORS headers for preflight request + if (request.verb() == "OPTIONS") { + // Allows GET requests from any origin with the Content-Type header and + // caches preflight response for an 3600s + return gcf::HttpResponse{} + .set_result(gcf::HttpResponse::kNoContent) + .set_header("Access-Control-Allow-Origin", "*") + .set_header("Access-Control-Allow-Methods", "GET") + .set_header("Access-Control-Allow-Headers", "Content-Type") + .set_header("Access-Control-Max-Age", "3600"); + } + return gcf::HttpResponse{} - .set_result(gcf::HttpResponse::kNoContent) .set_header("Access-Control-Allow-Origin", "*") - .set_header("Access-Control-Allow-Methods", "GET") - .set_header("Access-Control-Allow-Headers", "Content-Type") - .set_header("Access-Control-Max-Age", "3600"); - } - - return gcf::HttpResponse{} - .set_header("Access-Control-Allow-Origin", "*") - .set_header("content-type", "text/plain") - .set_payload("Hello World!"); + .set_header("content-type", "text/plain") + .set_payload("Hello World!"); + }); } // [END functions_http_cors] diff --git a/examples/site/http_cors_auth/http_cors_auth.cc b/examples/site/http_cors_auth/http_cors_auth.cc index 6ace8302..155d5600 100644 --- a/examples/site/http_cors_auth/http_cors_auth.cc +++ b/examples/site/http_cors_auth/http_cors_auth.cc @@ -13,29 +13,30 @@ // limitations under the License. // [START functions_http_cors_auth] -#include -#include +#include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse http_cors_auth(gcf::HttpRequest request) { // NOLINT - // Set CORS headers for preflight request - if (request.verb() == "OPTIONS") { - // Allows GET requests from any origin with the Content-Type header and - // caches preflight response for an 3600s +gcf::Function http_cors_auth() { + return gcf::MakeFunction([](gcf::HttpRequest const& request) { + // Set CORS headers for preflight request + if (request.verb() == "OPTIONS") { + // Allows GET requests from any origin with the Content-Type header and + // caches preflight response for an 3600s + return gcf::HttpResponse{} + .set_result(gcf::HttpResponse::kNoContent) + .set_header("Access-Control-Allow-Origin", "https://mydomain.com") + .set_header("Access-Control-Allow-Methods", "GET") + .set_header("Access-Control-Allow-Headers", "Authorization") + .set_header("Access-Control-Max-Age", "3600") + .set_header("Access-Control-Allow-Credentials", "true"); + } + return gcf::HttpResponse{} - .set_result(gcf::HttpResponse::kNoContent) .set_header("Access-Control-Allow-Origin", "https://mydomain.com") - .set_header("Access-Control-Allow-Methods", "GET") - .set_header("Access-Control-Allow-Headers", "Authorization") - .set_header("Access-Control-Max-Age", "3600") - .set_header("Access-Control-Allow-Credentials", "true"); - } - - return gcf::HttpResponse{} - .set_header("Access-Control-Allow-Origin", "https://mydomain.com") - .set_header("Access-Control-Allow-Credentials", "true") - .set_header("content-type", "text/plain") - .set_payload("Hello World!"); + .set_header("Access-Control-Allow-Credentials", "true") + .set_header("content-type", "text/plain") + .set_payload("Hello World!"); + }); } // [END functions_http_cors_auth] diff --git a/examples/site/http_form_data/http_form_data.cc b/examples/site/http_form_data/http_form_data.cc index 58254c23..06f4aee5 100644 --- a/examples/site/http_form_data/http_form_data.cc +++ b/examples/site/http_form_data/http_form_data.cc @@ -13,8 +13,7 @@ // limitations under the License. // [START functions_http_form_data] -#include -#include +#include #include #include #include @@ -59,7 +58,7 @@ class FormDataDelimiter { } // namespace -gcf::HttpResponse http_form_data(gcf::HttpRequest request) { +gcf::HttpResponse http_form_data_impl(gcf::HttpRequest request) { if (request.verb() != "POST") { return gcf::HttpResponse{}.set_result(gcf::HttpResponse::kMethodNotAllowed); } @@ -80,10 +79,9 @@ gcf::HttpResponse http_form_data(gcf::HttpRequest request) { for (auto& p : parts) { std::vector components = absl::StrSplit(p, absl::MaxSplits("\r\n\r\n", 2)); - auto const body_size = - components.size() == 2 ? components[0].size() : std::size_t(0); + auto const body_size = components.size() == 2 ? components[0].size() : 0; - std::vector part_headers = + std::vector const part_headers = absl::StrSplit(components[0], "\r\n"); nlohmann::json descriptor{{"bodySize", body_size}, {"headerCount", part_headers.size()}}; @@ -119,6 +117,10 @@ gcf::HttpResponse http_form_data(gcf::HttpRequest request) { .set_header("content-type", "application/json") .set_payload(result.dump()); } + +gcf::Function http_form_data() { + return gcf::MakeFunction(http_form_data_impl); +} // [END functions_http_form_data] namespace { diff --git a/examples/site/http_method/http_method.cc b/examples/site/http_method/http_method.cc index 13e17f91..c5a15aae 100644 --- a/examples/site/http_method/http_method.cc +++ b/examples/site/http_method/http_method.cc @@ -13,20 +13,21 @@ // limitations under the License. // [START functions_http_method] -#include -#include +#include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse http_method(gcf::HttpRequest request) { // NOLINT - if (request.verb() == "GET") { - return gcf::HttpResponse{} - .set_header("content-type", "text/plain") - .set_payload("Hello World!"); - } +gcf::Function http_method() { + return gcf::MakeFunction([](gcf::HttpRequest const& request) { + if (request.verb() == "GET") { + return gcf::HttpResponse{} + .set_header("content-type", "text/plain") + .set_payload("Hello World!"); + } - return gcf::HttpResponse{}.set_result( - request.verb() == "POST" ? gcf::HttpResponse::kForbidden - : gcf::HttpResponse::kMethodNotAllowed); + return gcf::HttpResponse{}.set_result( + request.verb() == "POST" ? gcf::HttpResponse::kForbidden + : gcf::HttpResponse::kMethodNotAllowed); + }); } // [END functions_http_method] diff --git a/examples/site/http_xml/http_xml.cc b/examples/site/http_xml/http_xml.cc index bf15227f..ac1062aa 100644 --- a/examples/site/http_xml/http_xml.cc +++ b/examples/site/http_xml/http_xml.cc @@ -13,25 +13,26 @@ // limitations under the License. // [START functions_http_xml] -#include -#include +#include #include #include #include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse http_xml(gcf::HttpRequest request) { // NOLINT - std::istringstream is(request.payload()); - // Use the Boost.PropertyTree XML parser, this is adequate for a small - // example, but application developers may want to consider a more robust - // parser for production code. - boost::property_tree::ptree data; - boost::property_tree::read_xml(is, data); +gcf::Function http_xml() { + return gcf::MakeFunction([](gcf::HttpRequest const& request) { + std::istringstream is(request.payload()); + // Use the Boost.PropertyTree XML parser, as this is adequate for a small + // example. Application developers may want to consider a more robust + // parser for production code. + boost::property_tree::ptree data; + boost::property_tree::read_xml(is, data); - auto name = data.get("name", "World"); - return gcf::HttpResponse{} - .set_header("content-type", "text/plain") - .set_payload("Hello " + name); + auto name = data.get("name", "World"); + return gcf::HttpResponse{} + .set_header("content-type", "text/plain") + .set_payload("Hello " + name); + }); } // [END functions_http_xml] diff --git a/examples/site/log_helloworld/log_helloworld.cc b/examples/site/log_helloworld/log_helloworld.cc index 71309f15..9b1abb33 100644 --- a/examples/site/log_helloworld/log_helloworld.cc +++ b/examples/site/log_helloworld/log_helloworld.cc @@ -13,23 +13,24 @@ // limitations under the License. // [START functions_log_helloworld] -#include -#include +#include #include #include namespace gcf = ::google::cloud::functions; -gcf::HttpResponse log_helloworld(gcf::HttpRequest /*request*/) { // NOLINT - std::cout << "This is stdout\n"; - std::cerr << "This is stderr\n"; +gcf::Function log_helloworld() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + std::cout << "This is stdout\n"; + std::cerr << "This is stderr\n"; - std::cerr << nlohmann::json{{"message", "This has ERROR severity"}, - {"severity", "error"}} - .dump() - << "\n"; - return gcf::HttpResponse{} - .set_header("content-type", "text/plain") - .set_payload("Hello Logging!"); + std::cerr << nlohmann::json{{"message", "This has ERROR severity"}, + {"severity", "error"}} + .dump() + << "\n"; + return gcf::HttpResponse{} + .set_header("content-type", "text/plain") + .set_payload("Hello Logging!"); + }); } // [END functions_log_helloworld] diff --git a/examples/site/log_stackdriver/log_stackdriver.cc b/examples/site/log_stackdriver/log_stackdriver.cc index 7997a1c7..219d8bcb 100644 --- a/examples/site/log_stackdriver/log_stackdriver.cc +++ b/examples/site/log_stackdriver/log_stackdriver.cc @@ -13,22 +13,24 @@ // limitations under the License. // [START functions_log_stackdriver] -#include +#include #include #include #include namespace gcf = ::google::cloud::functions; -void log_stackdriver(gcf::CloudEvent event) { // NOLINT - if (event.data_content_type().value_or("") != "application/json") { - std::cerr << "expected application/json data"; - return; - } - auto const payload = nlohmann::json::parse(event.data().value_or("{}")); - auto const data = cppcodec::base64_rfc4648::decode( - payload["message"]["data"].get()); - auto const log_entry = nlohmann::json::parse(data); - std::cout << "Log entry data: " << log_entry.dump(/*indent=*/2) << "\n"; +gcf::Function log_stackdriver() { + return gcf::MakeFunction([](gcf::CloudEvent const& event) { + if (event.data_content_type().value_or("") != "application/json") { + std::cerr << "expected application/json data\n"; + return; + } + auto const payload = nlohmann::json::parse(event.data().value_or("{}")); + auto const data = cppcodec::base64_rfc4648::decode( + payload["message"]["data"].get()); + auto const log_entry = nlohmann::json::parse(data); + std::cout << "Log entry data: " << log_entry.dump(/*indent=*/2) << "\n"; + }); } // [END functions_log_stackdriver] diff --git a/examples/site/pubsub_subscribe/pubsub_subscribe.cc b/examples/site/pubsub_subscribe/pubsub_subscribe.cc index 76c2771a..952e82a6 100644 --- a/examples/site/pubsub_subscribe/pubsub_subscribe.cc +++ b/examples/site/pubsub_subscribe/pubsub_subscribe.cc @@ -13,11 +13,15 @@ // limitations under the License. // [START functions_pubsub_subscribe] -#include +#include #include -void pubsub_subscribe(google::cloud::functions::CloudEvent event) { // NOLINT - // The framework converts the data from base64 if needed. - std::cout << event.data().value_or("") << "\n"; +namespace gcf = ::google::cloud::functions; + +gcf::Function pubsub_subscribe() { + return gcf::MakeFunction([](gcf::CloudEvent const& event) { + // The framework converts the data from base64 if needed. + std::cout << event.data().value_or("") << "\n"; + }); } // [END functions_pubsub_subscribe] diff --git a/examples/site/testing_http/http_integration_server.cc b/examples/site/testing_http/http_integration_server.cc index 5d8e3abd..09727aec 100644 --- a/examples/site/testing_http/http_integration_server.cc +++ b/examples/site/testing_http/http_integration_server.cc @@ -15,8 +15,8 @@ #include namespace gcf = ::google::cloud::functions; -extern gcf::HttpResponse hello_world_http(gcf::HttpRequest request); +extern gcf::Function hello_world_http(); int main(int argc, char* argv[]) { - return gcf::Run(argc, argv, hello_world_http); + return gcf::Run(argc, argv, hello_world_http()); } diff --git a/examples/site/testing_http/http_unit_test.cc b/examples/site/testing_http/http_unit_test.cc index 040592e3..ce13067c 100644 --- a/examples/site/testing_http/http_unit_test.cc +++ b/examples/site/testing_http/http_unit_test.cc @@ -18,20 +18,20 @@ #include namespace gcf = ::google::cloud::functions; -extern gcf::HttpResponse hello_world_http(gcf::HttpRequest request); +extern gcf::HttpResponse hello_world_http_impl(gcf::HttpRequest request); namespace { TEST(HttpUnitTest, Success) { - auto actual = hello_world_http( + auto actual = hello_world_http_impl( gcf::HttpRequest{}.set_payload(R"js({ "name": "Foo" })js")); EXPECT_EQ(actual.payload(), "Hello Foo!"); - actual = hello_world_http( + actual = hello_world_http_impl( gcf::HttpRequest{}.set_payload(R"js({ "unused": 7 })js")); EXPECT_EQ(actual.payload(), "Hello World!"); - actual = hello_world_http(gcf::HttpRequest{}.set_payload("Bar")); + actual = hello_world_http_impl(gcf::HttpRequest{}.set_payload("Bar")); EXPECT_EQ(actual.payload(), "Hello World!"); } diff --git a/examples/site/testing_pubsub/README.md b/examples/site/testing_pubsub/README.md index 889a15ac..2bcd6958 100644 --- a/examples/site/testing_pubsub/README.md +++ b/examples/site/testing_pubsub/README.md @@ -1,24 +1,25 @@ # How-to Guide: Testing Event-driven Functions (Pub/Sub triggered) Event-driven functions do not return values, their only observable behavior are -side-effects. All tests, therefore, are looking at the side-effects of these +side effects. All tests, therefore, are looking at the side effects of these functions. Depending on where the function is deployed this might be more or less involved. ## Function under Test -We will use this function throughput this guide: +We will use this [function][snippet source] throughput this guide: -[/examples/site/hello_world_pubsub/hello_world_pubsub.cc] + +[snippet source]: /examples/site/hello_world_pubsub/hello_world_pubsub.cc ```cc -#include +#include #include #include #include namespace gcf = ::google::cloud::functions; -void hello_world_pubsub(gcf::CloudEvent event) { +void hello_world_pubsub_impl(gcf::CloudEvent const& event) { if (event.data_content_type().value_or("") != "application/json") { BOOST_LOG_TRIVIAL(error) << "expected application/json data"; return; @@ -28,7 +29,12 @@ void hello_world_pubsub(gcf::CloudEvent event) { payload["message"]["data"].get()); BOOST_LOG_TRIVIAL(info) << "Hello " << (name.empty() ? "World" : name); } + +gcf::Function hello_world_pubsub() { + return gcf::MakeFunction(hello_world_pubsub_impl); +} ``` + ## Writing a Unit Test @@ -110,7 +116,6 @@ env "GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT}" \ [buildpacks]: https://buildpacks.io [boost-log-gh]: https://github.com/boostorg/log -[/examples/site/hello_world_pubsub/hello_world_pubsub.cc]: /examples/site/hello_world_pubsub/hello_world_pubsub.cc [pubsub_unit_test.cc]: pubsub_unit_test.cc [pubsub_integration_server.cc]: pubsub_integration_server.cc [pubsub_integration_test.cc]: pubsub_integration_test.cc diff --git a/examples/site/testing_pubsub/pubsub_integration_server.cc b/examples/site/testing_pubsub/pubsub_integration_server.cc index d8f4a21b..2d71cef4 100644 --- a/examples/site/testing_pubsub/pubsub_integration_server.cc +++ b/examples/site/testing_pubsub/pubsub_integration_server.cc @@ -15,8 +15,8 @@ #include namespace gcf = ::google::cloud::functions; -extern void hello_world_pubsub(gcf::CloudEvent event); +extern gcf::Function hello_world_pubsub(); int main(int argc, char* argv[]) { - return gcf::Run(argc, argv, hello_world_pubsub); + return gcf::Run(argc, argv, hello_world_pubsub()); } diff --git a/examples/site/testing_pubsub/pubsub_unit_test.cc b/examples/site/testing_pubsub/pubsub_unit_test.cc index a0dd1b07..140d83b0 100644 --- a/examples/site/testing_pubsub/pubsub_unit_test.cc +++ b/examples/site/testing_pubsub/pubsub_unit_test.cc @@ -24,7 +24,7 @@ #include namespace gcf = ::google::cloud::functions; -extern void hello_world_pubsub(gcf::CloudEvent event); +extern void hello_world_pubsub_impl(gcf::CloudEvent const& event); namespace { @@ -65,7 +65,7 @@ TEST(PubsubUnitTest, Basic) { event.set_data_content_type("application/json"); event.set_data(test.data); stream->str({}); - EXPECT_NO_THROW(hello_world_pubsub(event)); + EXPECT_NO_THROW(hello_world_pubsub_impl(event)); auto log_lines = stream->str(); EXPECT_THAT(log_lines, HasSubstr(test.expected)); } diff --git a/examples/site/testing_storage/README.md b/examples/site/testing_storage/README.md index c8b1e978..a0aff3a5 100644 --- a/examples/site/testing_storage/README.md +++ b/examples/site/testing_storage/README.md @@ -8,17 +8,18 @@ more or less involved. ## Function under Test -We will use this function throughout this guide: +We will use this [function][snippet source] throughout this guide: -[/examples/site/hello_world_storage/hello_world_storage.cc] + +[snippet source]: /examples/site/hello_world_storage/hello_world_storage.cc ```cc -#include +#include #include #include namespace gcf = ::google::cloud::functions; -void hello_world_storage(gcf::CloudEvent event) { +void hello_world_storage_impl(gcf::CloudEvent const& event) { if (event.data_content_type().value_or("") != "application/json") { BOOST_LOG_TRIVIAL(error) << "expected application/json data"; return; @@ -33,7 +34,12 @@ void hello_world_storage(gcf::CloudEvent event) { BOOST_LOG_TRIVIAL(info) << "Created: " << payload.value("timeCreated", ""); BOOST_LOG_TRIVIAL(info) << "Updated: " << payload.value("updated", ""); } + +gcf::Function hello_world_storage() { + return gcf::MakeFunction(hello_world_storage_impl); +} ``` + This test receives storage events and logs a few of the fields in these events. @@ -61,8 +67,7 @@ We will create a container for the storage "hello world" function as usual: ```shell pack build \ - --builder gcr.io/buildpacks/builder:latest \ - --env "GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent" \ + --builder gcr.io/buildpacks/builder:v1 \ --env "GOOGLE_FUNCTION_TARGET=hello_world_storage" \ --path "examples/site/hello_world_storage" \ "gcr.io/${GOOGLE_CLOUD_PROJECT}/gcf-hello-world-storage" @@ -133,7 +138,6 @@ gcloud container images delete \ [buildpacks]: https://buildpacks.io [boost-log-gh]: https://github.com/boostorg/log -[/examples/site/hello_world_storage/hello_world_storage.cc]: /examples/site/hello_world_storage/hello_world_storage.cc [storage_unit_test.cc]: storage_unit_test.cc [storage_integration_server.cc]: storage_integration_server.cc [storage_integration_test.cc]: storage_integration_test.cc diff --git a/examples/site/testing_storage/storage_integration_server.cc b/examples/site/testing_storage/storage_integration_server.cc index f8781910..ee480736 100644 --- a/examples/site/testing_storage/storage_integration_server.cc +++ b/examples/site/testing_storage/storage_integration_server.cc @@ -15,8 +15,8 @@ #include namespace gcf = ::google::cloud::functions; -extern void hello_world_storage(gcf::CloudEvent event); +extern gcf::Function hello_world_storage(); int main(int argc, char* argv[]) { - return gcf::Run(argc, argv, hello_world_storage); + return gcf::Run(argc, argv, hello_world_storage()); } diff --git a/examples/site/testing_storage/storage_unit_test.cc b/examples/site/testing_storage/storage_unit_test.cc index af1393ab..77781680 100644 --- a/examples/site/testing_storage/storage_unit_test.cc +++ b/examples/site/testing_storage/storage_unit_test.cc @@ -24,7 +24,7 @@ #include namespace gcf = ::google::cloud::functions; -extern void hello_world_storage(gcf::CloudEvent event); +extern void hello_world_storage_impl(gcf::CloudEvent const& event); namespace { @@ -74,7 +74,7 @@ TEST(StorageUnitTest, Basic) { data["name"] = test.name; event.set_data(data.dump()); stream->str({}); - EXPECT_NO_THROW(hello_world_storage(event)); + EXPECT_NO_THROW(hello_world_storage_impl(event)); auto log_lines = stream->str(); EXPECT_THAT(log_lines, HasSubstr(test.expected)); } diff --git a/examples/site/tips_gcp_apis/tips_gcp_apis.cc b/examples/site/tips_gcp_apis/tips_gcp_apis.cc index 2fbfdebc..5ce1843c 100644 --- a/examples/site/tips_gcp_apis/tips_gcp_apis.cc +++ b/examples/site/tips_gcp_apis/tips_gcp_apis.cc @@ -14,8 +14,7 @@ // [START functions_pubsub_publish] // [START functions_tips_gcp_apis] -#include -#include +#include #include #include #include @@ -44,7 +43,7 @@ pubsub::Publisher GetPublisher(pubsub::Topic topic) { } } // namespace -gcf::HttpResponse tips_gcp_apis(gcf::HttpRequest request) { // NOLINT +gcf::HttpResponse tips_gcp_apis_impl(gcf::HttpRequest const& request) { auto const* project = std::getenv("GCP_PROJECT"); if (project == nullptr) throw std::runtime_error("GCP_PROJECT is not set"); @@ -65,5 +64,7 @@ gcf::HttpResponse tips_gcp_apis(gcf::HttpRequest request) { // NOLINT } return response; } + +gcf::Function tips_gcp_apis() { return gcf::MakeFunction(tips_gcp_apis_impl); } // [END functions_tips_gcp_apis] // [END functions_pubsub_publish] diff --git a/examples/site/tips_infinite_retries/tips_infinite_retries.cc b/examples/site/tips_infinite_retries/tips_infinite_retries.cc index d5f64acd..6cc6f296 100644 --- a/examples/site/tips_infinite_retries/tips_infinite_retries.cc +++ b/examples/site/tips_infinite_retries/tips_infinite_retries.cc @@ -13,7 +13,7 @@ // limitations under the License. // [START functions_tips_infinite_retries] -#include +#include #include #include @@ -23,17 +23,19 @@ namespace { auto constexpr kMaxAge = std::chrono::seconds(10); } // namespace -void tips_infinite_retries(gcf::CloudEvent event) { // NOLINT - using std::chrono::system_clock; - auto const age = - system_clock::now() - event.time().value_or(system_clock::time_point()); - auto const seconds = - std::chrono::duration_cast(age).count(); +gcf::Function tips_infinite_retries() { + return gcf::MakeFunction([](gcf::CloudEvent const& event) { + using std::chrono::system_clock; + auto const age = + system_clock::now() - event.time().value_or(system_clock::time_point()); + auto const seconds = + std::chrono::duration_cast(age).count(); - if (age >= kMaxAge) { - std::cout << "Dropped " << event.id() << " (age " << seconds << "s)\n"; - return; - } - std::cout << "Processed " << event.id() << " (age " << seconds << "s)\n"; + if (age >= kMaxAge) { + std::cout << "Dropped " << event.id() << " (age " << seconds << "s)\n"; + return; + } + std::cout << "Processed " << event.id() << " (age " << seconds << "s)\n"; + }); } // [END functions_tips_infinite_retries] diff --git a/examples/site/tips_lazy_globals/tips_lazy_globals.cc b/examples/site/tips_lazy_globals/tips_lazy_globals.cc index e6f0898d..76db078a 100644 --- a/examples/site/tips_lazy_globals/tips_lazy_globals.cc +++ b/examples/site/tips_lazy_globals/tips_lazy_globals.cc @@ -13,8 +13,7 @@ // limitations under the License. // [START functions_tips_lazy_globals] -#include -#include +#include #include #include @@ -27,8 +26,10 @@ std::string h; void h_init() { h = "heavy computation"; } } // namespace -gcf::HttpResponse tips_lazy_globals(gcf::HttpRequest /*request*/) { // NOLINT - std::call_once(h_init_flag, h_init); - return gcf::HttpResponse{}.set_payload("Global: " + h); +gcf::Function tips_lazy_globals() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + std::call_once(h_init_flag, h_init); + return gcf::HttpResponse{}.set_payload("Global: " + h); + }); } // [END functions_tips_lazy_globals] diff --git a/examples/site/tips_retry/tips_retry.cc b/examples/site/tips_retry/tips_retry.cc index a70e557e..3c71beae 100644 --- a/examples/site/tips_retry/tips_retry.cc +++ b/examples/site/tips_retry/tips_retry.cc @@ -13,20 +13,22 @@ // limitations under the License. // [START functions_tips_retry] -#include +#include #include #include namespace gcf = ::google::cloud::functions; -void tips_retry(gcf::CloudEvent event) { // NOLINT - if (event.data_content_type().value_or("") != "application/json") { - std::cerr << "Error: expected application/json data\n"; - return; - } - auto const payload = nlohmann::json::parse(event.data().value_or("{}")); - auto const retry = payload.value("retry", false); - if (retry) throw std::runtime_error("Throwing exception to force retry"); - // Normal processing goes here. +gcf::Function tips_retry() { + return gcf::MakeFunction([](gcf::CloudEvent const& event) { + if (event.data_content_type().value_or("") != "application/json") { + std::cerr << "Error: expected application/json data\n"; + return; + } + auto const payload = nlohmann::json::parse(event.data().value_or("{}")); + auto const retry = payload.value("retry", false); + if (retry) throw std::runtime_error("Throwing exception to force retry"); + // Normal processing goes here. + }); } // [END functions_tips_retry] diff --git a/examples/site/tips_scopes/tips_scopes.cc b/examples/site/tips_scopes/tips_scopes.cc index 4f5a1184..fcff43aa 100644 --- a/examples/site/tips_scopes/tips_scopes.cc +++ b/examples/site/tips_scopes/tips_scopes.cc @@ -13,8 +13,7 @@ // limitations under the License. // [START functions_tips_scopes] -#include -#include +#include #include namespace gcf = ::google::cloud::functions; @@ -27,9 +26,11 @@ std::string light_computation(); std::string h = heavy_computation(); } // namespace -gcf::HttpResponse tips_scopes(gcf::HttpRequest /*request*/) { // NOLINT - auto l = light_computation(); - return gcf::HttpResponse{}.set_payload("Global: " + h + ", Local: " + l); +gcf::Function tips_scopes() { + return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) { + auto l = light_computation(); + return gcf::HttpResponse{}.set_payload("Global: " + h + ", Local: " + l); + }); } // [END functions_tips_scopes] diff --git a/examples/site/tutorial_cloud_bigtable/tutorial_cloud_bigtable.cc b/examples/site/tutorial_cloud_bigtable/tutorial_cloud_bigtable.cc index 0bbdb6e6..2db99308 100644 --- a/examples/site/tutorial_cloud_bigtable/tutorial_cloud_bigtable.cc +++ b/examples/site/tutorial_cloud_bigtable/tutorial_cloud_bigtable.cc @@ -14,8 +14,7 @@ // [START bigtable_functions_quickstart] #include -#include -#include +#include #include #include #include @@ -25,6 +24,8 @@ namespace gcf = ::google::cloud::functions; namespace cbt = ::google::cloud::bigtable; +namespace { + cbt::Table get_table_client(std::string project_id, std::string instance_id, std::string const& table_id) { static std::mutex mu; @@ -42,7 +43,7 @@ cbt::Table get_table_client(std::string project_id, std::string instance_id, return *table; } -gcf::HttpResponse tutorial_cloud_bigtable(gcf::HttpRequest request) { // NOLINT +gcf::HttpResponse handle_request(gcf::HttpRequest const& request) { auto get_header = [h = request.headers()](std::string key) { std::transform(key.begin(), key.end(), key.begin(), [](auto x) { return static_cast(std::tolower(x)); }); @@ -77,4 +78,11 @@ gcf::HttpResponse tutorial_cloud_bigtable(gcf::HttpRequest request) { // NOLINT .set_header("content-type", "text/plain") .set_payload(std::move(os).str()); } + +} // namespace + +gcf::Function tutorial_cloud_bigtable() { + return gcf::MakeFunction(handle_request); +} + // [END bigtable_functions_quickstart] diff --git a/examples/site/tutorial_cloud_spanner/tutorial_cloud_spanner.cc b/examples/site/tutorial_cloud_spanner/tutorial_cloud_spanner.cc index 88140d81..3b5efb25 100644 --- a/examples/site/tutorial_cloud_spanner/tutorial_cloud_spanner.cc +++ b/examples/site/tutorial_cloud_spanner/tutorial_cloud_spanner.cc @@ -13,8 +13,7 @@ // limitations under the License. // [START spanner_functions_quickstart] -#include -#include +#include #include #include #include @@ -25,6 +24,7 @@ namespace gcf = ::google::cloud::functions; namespace spanner = ::google::cloud::spanner; namespace { + auto get_spanner_database() { auto getenv = [](char const* var) { auto const* value = std::getenv(var); @@ -38,10 +38,8 @@ auto get_spanner_database() { getenv("SPANNER_INSTANCE"), getenv("SPANNER_DATABASE")); } -} // namespace -gcf::HttpResponse tutorial_cloud_spanner( - gcf::HttpRequest /*request*/) { // NOLINT +gcf::HttpResponse handle_request(gcf::HttpRequest const& /*request*/) { static auto const kClient = spanner::Client(spanner::MakeConnection(get_spanner_database())); @@ -62,4 +60,11 @@ gcf::HttpResponse tutorial_cloud_spanner( .set_header("content-type", "text/plain") .set_payload(std::move(os).str()); } + +} // namespace + +gcf::Function tutorial_cloud_spanner() { + return gcf::MakeFunction(handle_request); +} + // [END spanner_functions_quickstart] diff --git a/examples/site_test.cc b/examples/site_test.cc index b7f18cb6..1d91e000 100644 --- a/examples/site_test.cc +++ b/examples/site_test.cc @@ -12,9 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "google/cloud/functions/internal/function_impl.h" +#include "google/cloud/functions/internal/http_message_types.h" #include "google/cloud/functions/internal/parse_cloud_event_json.h" #include "google/cloud/functions/internal/setenv.h" #include "google/cloud/functions/cloud_event.h" +#include "google/cloud/functions/function.h" #include "google/cloud/functions/http_request.h" #include "google/cloud/functions/http_response.h" #include @@ -26,32 +29,34 @@ #include namespace gcf = ::google::cloud::functions; -extern gcf::HttpResponse bearer_token(gcf::HttpRequest request); -extern gcf::HttpResponse concepts_after_response(gcf::HttpRequest request); -extern gcf::HttpResponse concepts_after_timeout(gcf::HttpRequest request); -extern gcf::HttpResponse concepts_filesystem(gcf::HttpRequest request); -extern gcf::HttpResponse concepts_request(gcf::HttpRequest request); -extern gcf::HttpResponse concepts_stateless(gcf::HttpRequest request); -extern gcf::HttpResponse env_vars(gcf::HttpRequest request); -extern gcf::HttpResponse hello_world_error(gcf::HttpRequest request); -extern gcf::HttpResponse hello_world_get(gcf::HttpRequest request); -extern gcf::HttpResponse hello_world_http(gcf::HttpRequest request); -extern void hello_world_pubsub(gcf::CloudEvent event); -extern void hello_world_storage(gcf::CloudEvent event); -extern gcf::HttpResponse http_content(gcf::HttpRequest request); -extern gcf::HttpResponse http_cors(gcf::HttpRequest request); -extern gcf::HttpResponse http_cors_auth(gcf::HttpRequest request); -extern gcf::HttpResponse http_form_data(gcf::HttpRequest request); -extern gcf::HttpResponse http_method(gcf::HttpRequest request); -extern gcf::HttpResponse http_xml(gcf::HttpRequest request); -extern gcf::HttpResponse log_helloworld(gcf::HttpRequest request); -extern void log_stackdriver(gcf::CloudEvent event); -extern void pubsub_subscribe(gcf::CloudEvent event); -extern gcf::HttpResponse tips_gcp_apis(gcf::HttpRequest request); -extern void tips_infinite_retries(gcf::CloudEvent event); -extern gcf::HttpResponse tips_lazy_globals(gcf::HttpRequest request); -extern gcf::HttpResponse tips_scopes(gcf::HttpRequest request); -extern void tips_retry(gcf::CloudEvent event); +namespace gcf_internal = ::google::cloud::functions_internal; + +extern gcf::Function bearer_token(); +extern gcf::Function concepts_after_response(); +extern gcf::Function concepts_after_timeout(); +extern gcf::Function concepts_filesystem(); +extern gcf::Function concepts_request(); +extern gcf::Function concepts_stateless(); +extern gcf::Function env_vars(); +extern gcf::Function hello_world_error(); +extern gcf::Function hello_world_get(); +extern gcf::Function hello_world_http(); +extern gcf::Function hello_world_pubsub(); +extern gcf::Function hello_world_storage(); +extern gcf::Function http_content(); +extern gcf::Function http_cors(); +extern gcf::Function http_cors_auth(); +extern gcf::Function http_form_data(); +extern gcf::Function http_method(); +extern gcf::Function http_xml(); +extern gcf::Function log_helloworld(); +extern gcf::Function log_stackdriver(); +extern gcf::Function pubsub_subscribe(); +extern gcf::Function tips_gcp_apis(); +extern gcf::Function tips_infinite_retries(); +extern gcf::Function tips_lazy_globals(); +extern gcf::Function tips_scopes(); +extern gcf::Function tips_retry(); namespace { @@ -59,18 +64,64 @@ using ::testing::AllOf; using ::testing::HasSubstr; using ::testing::IsEmpty; +auto TriggerFunctionHttp(gcf::Function const& function, + gcf::HttpRequest const& r) { + gcf_internal::BeastRequest request; + request.method_string(r.verb()); + request.target(r.target()); + request.body() = r.payload(); + for (auto const& [k, v] : r.headers()) request.insert(k, v); + + auto handler = + gcf_internal::FunctionImpl::GetImpl(function)->GetHandler("unused"); + return handler(std::move(request)); +} + +auto TriggerFunctionCloudEvent(gcf::Function const& function, + gcf::CloudEvent const& e) { + auto payload = nlohmann::json{ + {"specversion", e.spec_version()}, + {"id", e.id()}, + {"source", e.source()}, + {"type", e.type()}, + }; + auto if_set = [&payload](std::string const& name, + std::optional const& v) { + if (v.has_value()) payload[name] = *v; + }; + if_set("datacontenttype", e.data_content_type()); + if_set("dataschema", e.data_schema()); + if_set("subject", e.subject()); + if (e.data_content_type().value_or("") == "application/json") { + payload["data"] = nlohmann::json::parse(e.data().value_or("{}")); + } else if (auto const& d = e.data(); d.has_value()) { + payload["data"] = *d; + } + + gcf_internal::BeastRequest request; + request.insert("content-type", "application/cloudevents+json"); + request.body() = payload.dump(); + + auto handler = + gcf_internal::FunctionImpl::GetImpl(function)->GetHandler("unused"); + return handler(std::move(request)); +} + TEST(ExamplesSiteTest, BearerToken) { google::cloud::functions_internal::SetEnv("TARGET_URL", std::nullopt); google::cloud::functions_internal::SetEnv("GOOGLE_APPLICATION_CREDENTIALS", "/dev/null"); - EXPECT_THROW(bearer_token(gcf::HttpRequest{}), std::exception); + auto function = bearer_token(); + EXPECT_EQ(TriggerFunctionHttp(function, gcf::HttpRequest{}).result_int(), + gcf::HttpResponse::kInternalServerError); google::cloud::functions_internal::SetEnv( "TARGET_URL", "https://storage.googleapis.com/storage/v1/" "b?project=invalid-project-name---"); - EXPECT_THROW(bearer_token(gcf::HttpRequest{}), std::exception); + EXPECT_EQ(TriggerFunctionHttp(function, gcf::HttpRequest{}).result_int(), + gcf::HttpResponse::kInternalServerError); // This is a syntactically valid JSON key file, but the key has been // invalidated and therefore presents no security risks. @@ -130,76 +181,91 @@ lUtj+/nH3HDQjM4ltYfTPUg= google::cloud::functions_internal::SetEnv("GOOGLE_APPLICATION_CREDENTIALS", filename.string()); - EXPECT_THROW(bearer_token(gcf::HttpRequest{}), std::exception); + // We get different errors in the CI builds vs. development workstations. + EXPECT_NE(TriggerFunctionHttp(function, gcf::HttpRequest{}).result_int(), + gcf::HttpResponse::kOkay); - EXPECT_NO_THROW( - bearer_token(gcf::HttpRequest{}.set_target("/no-auth-header"))); + EXPECT_EQ(TriggerFunctionHttp( + function, gcf::HttpRequest{}.set_target("/no-auth-header")) + .result_int(), + gcf::HttpResponse::kBadRequest); std::filesystem::remove(filename); } TEST(ExamplesSiteTest, ConceptsAfterResponse) { - auto actual = concepts_after_response(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("Hello World!")); + auto function = concepts_after_response(); + auto const actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_THAT(actual.body(), HasSubstr("Hello World!")); } TEST(ExamplesSiteTest, ConceptsAfterTimeout) { - auto actual = concepts_after_timeout(gcf::HttpRequest{}.set_verb("PUT")); - EXPECT_THAT(actual.payload(), HasSubstr("Function completed!")); + auto function = concepts_after_timeout(); + auto const actual = + TriggerFunctionHttp(function, gcf::HttpRequest{}.set_verb("PUT")); + EXPECT_THAT(actual.body(), HasSubstr("Function completed!")); } TEST(ExamplesSiteTest, ConceptsFilesystem) { - auto actual = concepts_filesystem(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), Not(IsEmpty())); + auto function = concepts_filesystem(); + auto const actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_THAT(actual.body(), Not(IsEmpty())); } TEST(ExamplesSiteTest, ConceptsRequest) { - auto actual = concepts_request(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("Received code")); + auto function = concepts_request(); + auto const actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_THAT(actual.body(), HasSubstr("Received code")); } TEST(ExamplesSiteTest, ConceptsStateless) { - auto actual = concepts_stateless(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("Instance execution count: ")); + auto function = concepts_stateless(); + auto const actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_THAT(actual.body(), HasSubstr("Instance execution count: ")); } TEST(ExamplesSiteTest, EnvVars) { google::cloud::functions_internal::SetEnv("FOO", std::nullopt); - auto actual = env_vars(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), AllOf(HasSubstr("FOO"), HasSubstr("not set"))); + auto function = env_vars(); + auto actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_THAT(actual.body(), AllOf(HasSubstr("FOO"), HasSubstr("not set"))); google::cloud::functions_internal::SetEnv("FOO", "test-value"); - actual = env_vars(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("test-value")); + actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_THAT(actual.body(), HasSubstr("test-value")); } TEST(ExamplesSiteTest, HelloWorldError) { - auto actual = hello_world_error(gcf::HttpRequest{}); - EXPECT_EQ(actual.payload(), "Hello World!"); - - EXPECT_THROW( - hello_world_error(gcf::HttpRequest{}.set_target("/throw/exception")), - std::exception); - auto error = hello_world_error(gcf::HttpRequest{}.set_target("/return500")); - EXPECT_EQ(error.result(), gcf::HttpResponse::kInternalServerError); + auto function = hello_world_error(); + auto actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_EQ(actual.body(), "Hello World!"); + + actual = TriggerFunctionHttp( + function, gcf::HttpRequest{}.set_target("/throw/exception")); + EXPECT_EQ(actual.result_int(), gcf::HttpResponse::kInternalServerError); + auto error = TriggerFunctionHttp(function, + gcf::HttpRequest{}.set_target("/return500")); + EXPECT_EQ(error.result_int(), gcf::HttpResponse::kInternalServerError); } TEST(ExamplesSiteTest, HelloWorldGet) { - auto actual = hello_world_get(gcf::HttpRequest{}); - EXPECT_EQ(actual.payload(), "Hello World!"); + auto function = hello_world_get(); + auto actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_EQ(actual.body(), "Hello World!"); } TEST(ExamplesSiteTest, HelloWorlHttp) { - auto actual = hello_world_http( - gcf::HttpRequest{}.set_payload(R"js({ "name": "Foo" })js")); - EXPECT_EQ(actual.payload(), "Hello Foo!"); + auto function = hello_world_http(); + auto actual = TriggerFunctionHttp( + function, gcf::HttpRequest{}.set_payload(R"js({ "name": "Foo" })js")); + EXPECT_EQ(actual.body(), "Hello Foo!"); - actual = hello_world_http( - gcf::HttpRequest{}.set_payload(R"js({ "unused": 7 })js")); - EXPECT_EQ(actual.payload(), "Hello World!"); + actual = TriggerFunctionHttp( + function, gcf::HttpRequest{}.set_payload(R"js({ "unused": 7 })js")); + EXPECT_EQ(actual.body(), "Hello World!"); - actual = hello_world_http(gcf::HttpRequest{}.set_payload("Bar")); - EXPECT_EQ(actual.payload(), "Hello World!"); + actual = TriggerFunctionHttp(function, gcf::HttpRequest{}.set_payload("Bar")); + EXPECT_EQ(actual.body(), "Hello World!"); } TEST(ExamplesSiteTest, HelloWorldPubSub) { @@ -228,12 +294,18 @@ TEST(ExamplesSiteTest, HelloWorldPubSub) { for (auto const* data : {"dGVzdCBtZXNzYWdlIDM=", "YWJjZA==", ""}) { auto json = base; json["data"]["message"]["data"] = data; - EXPECT_NO_THROW(hello_world_pubsub( - google::cloud::functions_internal::ParseCloudEventJson(json.dump()))); + auto response = TriggerFunctionHttp( + hello_world_pubsub(), + gcf::HttpRequest{} + .set_payload(json.dump()) + .add_header("content-type", "application/cloudevents+json") + .add_header("ce-type", "com.example.someevent") + .add_header("ce-source", "/mycontext") + .add_header("ce-id", "A234-1234-1234")); + EXPECT_EQ(response.result_int(), 200); } - EXPECT_NO_THROW(hello_world_pubsub( - google::cloud::functions_internal::ParseCloudEventJson(R"js({ + auto constexpr kBodyDataText = R"js({ "specversion": "1.0", "type": "test.invalid.invalid", "source": "//pubsub.googleapis.com/projects/sample-project/topics/gcf-test", @@ -241,10 +313,8 @@ TEST(ExamplesSiteTest, HelloWorldPubSub) { "time": "2020-09-29T11:32:00.000Z", "datacontenttype": "text/plain", "data": "some data" - })js"))); - - EXPECT_THROW(hello_world_pubsub( - google::cloud::functions_internal::ParseCloudEventJson(R"js({ + })js"; + auto constexpr kBodyDataJson = R"js({ "specversion": "1.0", "type": "google.cloud.pubsub.topic.v1.messagePublished", "source": "//pubsub.googleapis.com/projects/sample-project/topics/gcf-test", @@ -255,8 +325,27 @@ TEST(ExamplesSiteTest, HelloWorldPubSub) { "subscription": "projects/sample-project/subscriptions/sample-subscription" } } - })js")), - std::exception); + })js"; + + struct TestCase { + std::string name; + std::string body; + } const cases[] = { + {"text", kBodyDataText}, + {"json", kBodyDataJson}, + }; + + for (auto const& [name, body] : cases) { + SCOPED_TRACE("Testing for " + name); + auto response = + TriggerFunctionHttp(hello_world_pubsub(), + gcf::HttpRequest{} + .set_payload(body) + .add_header("ce-type", "com.example.someevent") + .add_header("ce-source", "/mycontext") + .add_header("ce-id", "A234-1234-1234")); + EXPECT_EQ(response.result_int(), 200); + } } TEST(ExamplesSiteTest, HelloWorldStorage) { @@ -291,11 +380,7 @@ TEST(ExamplesSiteTest, HelloWorldStorage) { } })js"); - EXPECT_NO_THROW(hello_world_storage( - google::cloud::functions_internal::ParseCloudEventJson(base.dump()))); - - EXPECT_NO_THROW(hello_world_storage( - google::cloud::functions_internal::ParseCloudEventJson(R"js({ + auto constexpr kBodyDataText = R"js({ "specversion": "1.0", "type": "test.invalid.invalid", "source": "//pubsub.googleapis.com/projects/sample-project/topics/gcf-test", @@ -303,17 +388,39 @@ TEST(ExamplesSiteTest, HelloWorldStorage) { "time": "2020-09-29T11:32:00.000Z", "datacontenttype": "text/plain", "data": "some data" - })js"))); + })js"; - EXPECT_NO_THROW(hello_world_storage( - google::cloud::functions_internal::ParseCloudEventJson(R"js({ + auto constexpr kBodyDataJson = R"js({ "specversion": "1.0", "type": "test.invalid.invalid", "source": "//pubsub.googleapis.com/projects/sample-project/topics/gcf-test", "id": "aaaaaa-1111-bbbb-2222-cccccccccccc", "time": "2020-09-29T11:32:00.000Z", "datacontenttype": "application/json" - })js"))); + })js"; + + struct TestCase { + std::string name; + std::string body; + std::string content_type; + } const cases[] = { + {"base", base.dump(), "application/cloudevents+json"}, + {"text", kBodyDataText, "text/plain"}, + {"json", kBodyDataJson, "application/cloudevents+json"}, + }; + + for (auto const& [name, body, content_type] : cases) { + SCOPED_TRACE("Testing for " + name); + auto response = + TriggerFunctionHttp(hello_world_storage(), + gcf::HttpRequest{} + .set_payload(body) + .add_header("content-type", content_type) + .add_header("ce-type", "com.example.someevent") + .add_header("ce-source", "/mycontext") + .add_header("ce-id", "A234-1234-1234")); + EXPECT_EQ(response.result_int(), 200); + } } TEST(ExamplesSiteTest, HttpContent) { @@ -323,41 +430,46 @@ TEST(ExamplesSiteTest, HttpContent) { .set_payload(std::move(payload)); }; - auto actual = http_content( - make_request("application/json", R"js({ "name": "Foo" })js")); - EXPECT_THAT(actual.payload(), "Hello Foo"); + auto function = http_content(); + auto actual = TriggerFunctionHttp( + function, make_request("application/json", R"js({ "name": "Foo" })js")); + EXPECT_THAT(actual.body(), "Hello Foo"); - actual = http_content(make_request("text/plain", "Bar")); - EXPECT_THAT(actual.payload(), "Hello Bar"); + actual = TriggerFunctionHttp(function, make_request("text/plain", "Bar")); + EXPECT_THAT(actual.body(), "Hello Bar"); - actual = http_content(make_request("application/x-www-form-urlencoded", - "id=1&name=Baz%20Qux&value=x")); - EXPECT_THAT(actual.payload(), "Hello Baz Qux"); + actual = TriggerFunctionHttp(function, + make_request("application/x-www-form-urlencoded", + "id=1&name=Baz%20Qux&value=x")); + EXPECT_THAT(actual.body(), "Hello Baz Qux"); - actual = http_content(make_request("application/x-www-form-urlencoded", - "id=1&name=Baz%Qux&value=x")); - EXPECT_THAT(actual.payload(), "Hello Baz%Qux"); + actual = TriggerFunctionHttp(function, + make_request("application/x-www-form-urlencoded", + "id=1&name=Baz%Qux&value=x")); + EXPECT_THAT(actual.body(), "Hello Baz%Qux"); } TEST(ExamplesSiteTest, HttpCors) { - auto actual = http_cors(gcf::HttpRequest{}.set_verb("OPTIONS")); - EXPECT_EQ(actual.headers().at("Access-Control-Allow-Methods"), "GET"); - - actual = http_cors(gcf::HttpRequest{}.set_verb("GET")); - EXPECT_EQ(actual.headers().at("Access-Control-Allow-Origin"), "*"); - EXPECT_EQ(actual.payload(), "Hello World!"); + auto function = http_cors(); + auto actual = + TriggerFunctionHttp(function, gcf::HttpRequest{}.set_verb("OPTIONS")); + EXPECT_EQ(actual.at("Access-Control-Allow-Methods"), "GET"); + + actual = TriggerFunctionHttp(function, gcf::HttpRequest{}.set_verb("GET")); + EXPECT_EQ(actual.at("Access-Control-Allow-Origin"), "*"); + EXPECT_EQ(actual.body(), "Hello World!"); } TEST(ExamplesSiteTest, HttpCorsAuth) { - auto actual = http_cors_auth(gcf::HttpRequest{}.set_verb("OPTIONS")); - EXPECT_EQ(actual.headers().at("Access-Control-Allow-Headers"), - "Authorization"); - - actual = http_cors_auth(gcf::HttpRequest{}.set_verb("GET")); - EXPECT_EQ(actual.headers().at("Access-Control-Allow-Origin"), - "https://mydomain.com"); - EXPECT_EQ(actual.headers().at("Access-Control-Allow-Credentials"), "true"); - EXPECT_EQ(actual.payload(), "Hello World!"); + auto function = http_cors_auth(); + auto actual = + TriggerFunctionHttp(function, gcf::HttpRequest{}.set_verb("OPTIONS")); + EXPECT_EQ(actual.at("Access-Control-Allow-Headers"), "Authorization"); + + actual = TriggerFunctionHttp(function, gcf::HttpRequest{}.set_verb("GET")); + EXPECT_EQ(actual.at("Access-Control-Allow-Origin"), "https://mydomain.com"); + EXPECT_EQ(actual.at("Access-Control-Allow-Credentials"), "true"); + EXPECT_EQ(actual.body(), "Hello World!"); } TEST(ExamplesSiteTest, HttpFormData) { @@ -376,16 +488,18 @@ TEST(ExamplesSiteTest, HttpFormData) { ; // Test with both quoted and unquoted boundaries. + auto function = http_form_data(); for (auto const* content_type : {R"""(multipart/form-data;boundary="boundary")""", R"""(multipart/form-data;boundary=boundary)"""}) { SCOPED_TRACE("Testing with content_type = " + std::string(content_type)); - auto actual = http_form_data(gcf::HttpRequest{} - .add_header("content-type", content_type) - .set_payload(kPayload) - .set_verb("POST")); - ASSERT_EQ(actual.result(), gcf::HttpResponse::kOkay); - auto const actual_payload = nlohmann::json::parse(actual.payload()); + auto actual = TriggerFunctionHttp( + function, gcf::HttpRequest{} + .add_header("content-type", content_type) + .set_payload(kPayload) + .set_verb("POST")); + ASSERT_EQ(actual.result_int(), gcf::HttpResponse::kOkay); + auto const actual_payload = nlohmann::json::parse(actual.body()); auto const expected_payload = nlohmann::json{ {"parts", { @@ -398,34 +512,39 @@ TEST(ExamplesSiteTest, HttpFormData) { EXPECT_EQ(actual_payload, expected_payload); } - EXPECT_EQ(http_form_data(gcf::HttpRequest{}).result(), + EXPECT_EQ(TriggerFunctionHttp(function, gcf::HttpRequest{}).result_int(), gcf::HttpResponse::kMethodNotAllowed); - EXPECT_EQ(http_form_data(gcf::HttpRequest{}.set_verb("POST")).result(), + EXPECT_EQ(TriggerFunctionHttp(function, gcf::HttpRequest{}.set_verb("POST")) + .result_int(), gcf::HttpResponse::kBadRequest); - EXPECT_EQ(http_form_data(gcf::HttpRequest{}.set_verb("POST").add_header( - "content-type", "application/json")) - .result(), + EXPECT_EQ(TriggerFunctionHttp(function, + gcf::HttpRequest{}.set_verb("POST").add_header( + "content-type", "application/json")) + .result_int(), gcf::HttpResponse::kBadRequest); - EXPECT_THROW( - http_form_data(gcf::HttpRequest{} - .add_header("content-type", "multipart/form-data") - .set_verb("POST")), - std::exception); + EXPECT_EQ(TriggerFunctionHttp( + function, gcf::HttpRequest{} + .add_header("content-type", "multipart/form-data") + .set_verb("POST")) + .result_int(), + gcf::HttpResponse::kInternalServerError); } TEST(ExamplesSiteTest, HttpMethod) { struct Test { std::string verb; int result; - } tests[] = { + } const tests[] = { {"GET", gcf::HttpResponse::kOkay}, {"POST", gcf::HttpResponse::kForbidden}, {"PUT", gcf::HttpResponse::kMethodNotAllowed}, }; + auto function = http_method(); for (auto const& test : tests) { - auto actual = http_method(gcf::HttpRequest{}.set_verb(test.verb)); - EXPECT_EQ(actual.result(), test.result); + auto actual = + TriggerFunctionHttp(function, gcf::HttpRequest{}.set_verb(test.verb)); + EXPECT_EQ(actual.result_int(), test.result); } } @@ -434,24 +553,26 @@ TEST(ExamplesSiteTest, HttpXml) { return gcf::HttpRequest{}.set_payload(std::move(payload)); }; - auto actual = http_xml(make_request(R"xml( + auto function = http_xml(); + auto actual = TriggerFunctionHttp(function, make_request(R"xml( Foo Bar Baz )xml")); - EXPECT_EQ(actual.payload(), "Hello Foo"); + EXPECT_EQ(actual.body(), "Hello Foo"); - actual = http_xml(make_request(R"xml( + actual = TriggerFunctionHttp(function, make_request(R"xml( Foo Bar Baz )xml")); - EXPECT_EQ(actual.payload(), "Hello World"); + EXPECT_EQ(actual.body(), "Hello World"); } TEST(ExamplesSiteTest, LogHelloWorld) { - auto actual = log_helloworld(gcf::HttpRequest{}); - EXPECT_EQ(actual.payload(), "Hello Logging!"); + auto function = log_helloworld(); + auto actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_EQ(actual.body(), "Hello Logging!"); } TEST(ExamplesSiteTest, LogStackdriver) { @@ -490,13 +611,18 @@ TEST(ExamplesSiteTest, LogStackdriver) { }}, }; - EXPECT_NO_THROW(log_stackdriver( - google::cloud::functions_internal::ParseCloudEventJson(envelope.dump()))); + auto const event = + google::cloud::functions_internal::ParseCloudEventJson(envelope.dump()); + auto function = log_stackdriver(); + EXPECT_EQ(TriggerFunctionCloudEvent(function, event).result_int(), + gcf::HttpResponse::kOkay); // This is just to fix the code coverage nit. envelope.erase("datacontenttype"); - EXPECT_NO_THROW(log_stackdriver( - google::cloud::functions_internal::ParseCloudEventJson(envelope.dump()))); + auto const bad = + google::cloud::functions_internal::ParseCloudEventJson(envelope.dump()); + EXPECT_EQ(TriggerFunctionCloudEvent(function, bad).result_int(), + gcf::HttpResponse::kOkay); } TEST(ExamplesSiteTest, PubsubSubscribe) { @@ -522,19 +648,23 @@ TEST(ExamplesSiteTest, PubsubSubscribe) { })js"); // Test with different values for data.message.data + auto function = pubsub_subscribe(); for (auto const* data : {"dGVzdCBtZXNzYWdlIDM=", "YWJjZA==", ""}) { auto json = base; json["data"]["message"]["data"] = data; - EXPECT_NO_THROW(pubsub_subscribe( - google::cloud::functions_internal::ParseCloudEventJson(json.dump()))); + auto const event = + google::cloud::functions_internal::ParseCloudEventJson(json.dump()); + EXPECT_EQ(TriggerFunctionCloudEvent(function, event).result_int(), + gcf::HttpResponse::kOkay); } } TEST(ExamplesSiteTest, TipsLazyGlobals) { - auto actual = tips_lazy_globals(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("heavy computation")); - actual = tips_lazy_globals(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("heavy computation")); + auto function = tips_lazy_globals(); + auto actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_THAT(actual.body(), HasSubstr("heavy computation")); + actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_THAT(actual.body(), HasSubstr("heavy computation")); } TEST(ExamplesSiteTest, TipsGcpApis) { @@ -545,37 +675,50 @@ TEST(ExamplesSiteTest, TipsGcpApis) { #endif // __has_feature(thread_sanitizer) #endif // defined(__has_feature) google::cloud::functions_internal::SetEnv("GCP_PROJECT", std::nullopt); - EXPECT_THROW(tips_gcp_apis(gcf::HttpRequest{}), std::runtime_error); + auto function = tips_gcp_apis(); + EXPECT_EQ(TriggerFunctionHttp(function, gcf::HttpRequest{}).result_int(), + gcf::HttpResponse::kInternalServerError); google::cloud::functions_internal::SetEnv("GCP_PROJECT", "test-unused"); - EXPECT_THROW(tips_gcp_apis(gcf::HttpRequest{}), std::exception); - EXPECT_THROW( - tips_gcp_apis(gcf::HttpRequest{}.set_payload(nlohmann::json({}).dump())), - std::runtime_error); + EXPECT_EQ(TriggerFunctionHttp(function, gcf::HttpRequest{}).result_int(), + gcf::HttpResponse::kInternalServerError); + EXPECT_EQ(TriggerFunctionHttp(function, gcf::HttpRequest{}.set_payload( + nlohmann::json({}).dump())) + .result_int(), + gcf::HttpResponse::kInternalServerError); } TEST(ExamplesSiteTest, TipsInfiniteRetries) { + auto function = tips_infinite_retries(); gcf::CloudEvent event("test-id", "test-source", "test-type"); - EXPECT_NO_THROW(tips_infinite_retries(event)); + EXPECT_EQ(TriggerFunctionCloudEvent(function, event).result_int(), + gcf::HttpResponse::kOkay); event.set_time(std::chrono::system_clock::now()); - EXPECT_NO_THROW(tips_infinite_retries(event)); + EXPECT_EQ(TriggerFunctionCloudEvent(function, event).result_int(), + gcf::HttpResponse::kOkay); } TEST(ExamplesSiteTest, TipsScopes) { - auto actual = tips_scopes(gcf::HttpRequest{}); - EXPECT_THAT(actual.payload(), HasSubstr("Global: ")); - EXPECT_THAT(actual.payload(), HasSubstr("Local: ")); + auto function = tips_scopes(); + auto actual = TriggerFunctionHttp(function, gcf::HttpRequest{}); + EXPECT_THAT(actual.body(), HasSubstr("Global: ")); + EXPECT_THAT(actual.body(), HasSubstr("Local: ")); } TEST(ExamplesSiteTest, TipsRetry) { + auto function = tips_retry(); gcf::CloudEvent event("test-id", "test-source", "test-type"); - EXPECT_NO_THROW(tips_retry(event)); + EXPECT_EQ(TriggerFunctionCloudEvent(function, event).result_int(), + gcf::HttpResponse::kOkay); event.set_data_content_type("application/json"); - EXPECT_NO_THROW(tips_retry(event)); + EXPECT_EQ(TriggerFunctionCloudEvent(function, event).result_int(), + gcf::HttpResponse::kOkay); event.set_data(nlohmann::json({{"retry", false}}).dump()); - EXPECT_NO_THROW(tips_retry(event)); + EXPECT_EQ(TriggerFunctionCloudEvent(function, event).result_int(), + gcf::HttpResponse::kOkay); event.set_data(nlohmann::json({{"retry", true}}).dump()); - EXPECT_THROW(tips_retry(event), std::exception); + EXPECT_EQ(TriggerFunctionCloudEvent(function, event).result_int(), + gcf::HttpResponse::kInternalServerError); } } // namespace diff --git a/google/cloud/functions/internal/base64_decode.cc b/google/cloud/functions/internal/base64_decode.cc index 8d8a2625..958f55cb 100644 --- a/google/cloud/functions/internal/base64_decode.cc +++ b/google/cloud/functions/internal/base64_decode.cc @@ -20,6 +20,7 @@ namespace google::cloud::functions_internal { FUNCTIONS_FRAMEWORK_CPP_INLINE_NAMESPACE_BEGIN +// NOLINTNEXTLINE(misc-no-recursion) std::string Base64Decode(std::string const& base64) { if (base64.size() % 4 != 0) { // This should be uncommon, to avoid copying every time, pad only when diff --git a/google/cloud/functions/internal/parse_cloud_event_json.cc b/google/cloud/functions/internal/parse_cloud_event_json.cc index 2bc04562..4687119d 100644 --- a/google/cloud/functions/internal/parse_cloud_event_json.cc +++ b/google/cloud/functions/internal/parse_cloud_event_json.cc @@ -46,7 +46,7 @@ functions::CloudEvent ParseCloudEventJson(nlohmann::json const& json) { event.set_time(json.at("time").get()); } if (json.count("data") != 0) { - auto d = json.at("data"); + auto const& d = json.at("data"); if (d.is_object()) { event.set_data(d.dump()); } else { diff --git a/google/cloud/functions/internal/parse_cloud_event_legacy.cc b/google/cloud/functions/internal/parse_cloud_event_legacy.cc index ddd2db58..6ed0acba 100644 --- a/google/cloud/functions/internal/parse_cloud_event_legacy.cc +++ b/google/cloud/functions/internal/parse_cloud_event_legacy.cc @@ -21,6 +21,7 @@ namespace google::cloud::functions_internal { FUNCTIONS_FRAMEWORK_CPP_INLINE_NAMESPACE_BEGIN namespace { +// NOLINTNEXTLINE(misc-no-recursion) std::string GetNestedKey(nlohmann::json const& json, std::deque path) { if (path.empty()) return std::string{}; diff --git a/google/cloud/functions/internal/version_info.h b/google/cloud/functions/internal/version_info.h index 4fb2d171..2dca1acd 100644 --- a/google/cloud/functions/internal/version_info.h +++ b/google/cloud/functions/internal/version_info.h @@ -16,7 +16,7 @@ #define FUNCTIONS_FRAMEWORK_CPP_GOOGLE_CLOUD_FUNCTIONS_INTERNAL_VERSION_INFO_H #define FUNCTIONS_FRAMEWORK_CPP_VERSION_MAJOR 1 -#define FUNCTIONS_FRAMEWORK_CPP_VERSION_MINOR 1 +#define FUNCTIONS_FRAMEWORK_CPP_VERSION_MINOR 2 #define FUNCTIONS_FRAMEWORK_CPP_VERSION_PATCH 0 #endif // FUNCTIONS_FRAMEWORK_CPP_GOOGLE_CLOUD_FUNCTIONS_INTERNAL_VERSION_INFO_H