diff --git a/.github/pipeline-descriptor.yml b/.github/pipeline-descriptor.yml index e517e4e..1889e77 100644 --- a/.github/pipeline-descriptor.yml +++ b/.github/pipeline-descriptor.yml @@ -7,7 +7,7 @@ codeowners: owner: "@paketo-buildpacks/java-buildpacks" package: - repository: gcr.io/paketo-buildpacks/google-stackdriver + repository: gcr.io/paketo-buildpacks/google-cloud register: true registry_token: ${{ secrets.JAVA_GITHUB_TOKEN }} @@ -17,20 +17,20 @@ docker_credentials: password: ${{ secrets.JAVA_GCLOUD_SERVICE_ACCOUNT_KEY }} dependencies: -- id: google-stackdriver-debugger-java +- id: google-cloud-debugger-java uses: docker://ghcr.io/paketo-buildpacks/actions/github-release-dependency:main with: glob: compute-java_debian-wheezy_cdbg_java_agent_gce\.tar owner: GoogleCloudPlatform repository: cloud-debug-java token: ${{ secrets.JAVA_GITHUB_TOKEN }} -- id: google-stackdriver-debugger-nodejs +- id: google-cloud-debugger-nodejs uses: docker://ghcr.io/paketo-buildpacks/actions/npm-dependency:main with: package: "@google-cloud/debug-agent" -- id: google-stackdriver-profiler-java +- id: google-cloud-profiler-java uses: docker://ghcr.io/paketo-buildpacks/actions/google-stackdriver-profiler-dependency:main -- id: google-stackdriver-profiler-nodejs +- id: google-cloud-profiler-nodejs uses: docker://ghcr.io/paketo-buildpacks/actions/npm-dependency:main with: package: "@google-cloud/profiler" diff --git a/.github/workflows/create-package.yml b/.github/workflows/create-package.yml index 7d404ab..b2b3a61 100644 --- a/.github/workflows/create-package.yml +++ b/.github/workflows/create-package.yml @@ -137,7 +137,7 @@ jobs: --config "${HOME}"/package.toml fi env: - PACKAGE: gcr.io/paketo-buildpacks/google-stackdriver + PACKAGE: gcr.io/paketo-buildpacks/google-cloud PUBLISH: "true" VERSION: ${{ steps.version.outputs.version }} - name: Update release with digest @@ -164,7 +164,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.JAVA_GITHUB_TOKEN }} - uses: docker://ghcr.io/buildpacks/actions/registry:main with: - address: gcr.io/paketo-buildpacks/google-stackdriver@${{ steps.package.outputs.digest }} - id: paketo-buildpacks/google-stackdriver + address: gcr.io/paketo-buildpacks/google-cloud@${{ steps.package.outputs.digest }} + id: paketo-buildpacks/google-cloud token: ${{ secrets.JAVA_GITHUB_TOKEN }} version: ${{ steps.version.outputs.version }} diff --git a/.github/workflows/update-google-stackdriver-debugger-java.yml b/.github/workflows/update-google-cloud-debugger-java.yml similarity index 79% rename from .github/workflows/update-google-stackdriver-debugger-java.yml rename to .github/workflows/update-google-cloud-debugger-java.yml index d36059b..c5cee0b 100644 --- a/.github/workflows/update-google-stackdriver-debugger-java.yml +++ b/.github/workflows/update-google-cloud-debugger-java.yml @@ -1,4 +1,4 @@ -name: Update google-stackdriver-debugger-java +name: Update google-cloud-debugger-java "on": schedule: - cron: 0 5 * * 1-5 @@ -75,21 +75,21 @@ jobs: echo "::set-output name=old-version::${OLD_VERSION}" echo "::set-output name=new-version::${VERSION}" env: - ID: google-stackdriver-debugger-java + ID: google-cloud-debugger-java SHA256: ${{ steps.dependency.outputs.sha256 }} URI: ${{ steps.dependency.outputs.uri }} VERSION: ${{ steps.dependency.outputs.version }} VERSION_PATTERN: '[\d]+\.[\d]+\.[\d]+' - uses: peter-evans/create-pull-request@v3 with: - body: Bumps `google-stackdriver-debugger-java` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. - branch: update/buildpack/google-stackdriver-debugger-java + body: Bumps `google-cloud-debugger-java` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. + branch: update/buildpack/google-cloud-debugger-java commit-message: |- - Bump google-stackdriver-debugger-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} + Bump google-cloud-debugger-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} - Bumps google-stackdriver-debugger-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. + Bumps google-cloud-debugger-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. delete-branch: true labels: semver:minor, type:dependency-upgrade signoff: true - title: Bump google-stackdriver-debugger-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} + title: Bump google-cloud-debugger-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} token: ${{ secrets.JAVA_GITHUB_TOKEN }} diff --git a/.github/workflows/update-google-stackdriver-debugger-nodejs.yml b/.github/workflows/update-google-cloud-debugger-nodejs.yml similarity index 78% rename from .github/workflows/update-google-stackdriver-debugger-nodejs.yml rename to .github/workflows/update-google-cloud-debugger-nodejs.yml index e48fcbd..4da9a55 100644 --- a/.github/workflows/update-google-stackdriver-debugger-nodejs.yml +++ b/.github/workflows/update-google-cloud-debugger-nodejs.yml @@ -1,4 +1,4 @@ -name: Update google-stackdriver-debugger-nodejs +name: Update google-cloud-debugger-nodejs "on": schedule: - cron: 0 5 * * 1-5 @@ -72,21 +72,21 @@ jobs: echo "::set-output name=old-version::${OLD_VERSION}" echo "::set-output name=new-version::${VERSION}" env: - ID: google-stackdriver-debugger-nodejs + ID: google-cloud-debugger-nodejs SHA256: ${{ steps.dependency.outputs.sha256 }} URI: ${{ steps.dependency.outputs.uri }} VERSION: ${{ steps.dependency.outputs.version }} VERSION_PATTERN: '[\d]+\.[\d]+\.[\d]+' - uses: peter-evans/create-pull-request@v3 with: - body: Bumps `google-stackdriver-debugger-nodejs` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. - branch: update/buildpack/google-stackdriver-debugger-nodejs + body: Bumps `google-cloud-debugger-nodejs` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. + branch: update/buildpack/google-cloud-debugger-nodejs commit-message: |- - Bump google-stackdriver-debugger-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} + Bump google-cloud-debugger-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} - Bumps google-stackdriver-debugger-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. + Bumps google-cloud-debugger-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. delete-branch: true labels: semver:minor, type:dependency-upgrade signoff: true - title: Bump google-stackdriver-debugger-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} + title: Bump google-cloud-debugger-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} token: ${{ secrets.JAVA_GITHUB_TOKEN }} diff --git a/.github/workflows/update-google-stackdriver-profiler-java.yml b/.github/workflows/update-google-cloud-profiler-java.yml similarity index 78% rename from .github/workflows/update-google-stackdriver-profiler-java.yml rename to .github/workflows/update-google-cloud-profiler-java.yml index d2d6f5a..64499c6 100644 --- a/.github/workflows/update-google-stackdriver-profiler-java.yml +++ b/.github/workflows/update-google-cloud-profiler-java.yml @@ -1,4 +1,4 @@ -name: Update google-stackdriver-profiler-java +name: Update google-cloud-profiler-java "on": schedule: - cron: 0 5 * * 1-5 @@ -70,21 +70,21 @@ jobs: echo "::set-output name=old-version::${OLD_VERSION}" echo "::set-output name=new-version::${VERSION}" env: - ID: google-stackdriver-profiler-java + ID: google-cloud-profiler-java SHA256: ${{ steps.dependency.outputs.sha256 }} URI: ${{ steps.dependency.outputs.uri }} VERSION: ${{ steps.dependency.outputs.version }} VERSION_PATTERN: '[\d]+\.[\d]+\.[\d]+' - uses: peter-evans/create-pull-request@v3 with: - body: Bumps `google-stackdriver-profiler-java` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. - branch: update/buildpack/google-stackdriver-profiler-java + body: Bumps `google-cloud-profiler-java` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. + branch: update/buildpack/google-cloud-profiler-java commit-message: |- - Bump google-stackdriver-profiler-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} + Bump google-cloud-profiler-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} - Bumps google-stackdriver-profiler-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. + Bumps google-cloud-profiler-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. delete-branch: true labels: semver:minor, type:dependency-upgrade signoff: true - title: Bump google-stackdriver-profiler-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} + title: Bump google-cloud-profiler-java from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} token: ${{ secrets.JAVA_GITHUB_TOKEN }} diff --git a/.github/workflows/update-google-stackdriver-profiler-nodejs.yml b/.github/workflows/update-google-cloud-profiler-nodejs.yml similarity index 78% rename from .github/workflows/update-google-stackdriver-profiler-nodejs.yml rename to .github/workflows/update-google-cloud-profiler-nodejs.yml index b5b42b7..a230efb 100644 --- a/.github/workflows/update-google-stackdriver-profiler-nodejs.yml +++ b/.github/workflows/update-google-cloud-profiler-nodejs.yml @@ -1,4 +1,4 @@ -name: Update google-stackdriver-profiler-nodejs +name: Update google-cloud-profiler-nodejs "on": schedule: - cron: 0 5 * * 1-5 @@ -72,21 +72,21 @@ jobs: echo "::set-output name=old-version::${OLD_VERSION}" echo "::set-output name=new-version::${VERSION}" env: - ID: google-stackdriver-profiler-nodejs + ID: google-cloud-profiler-nodejs SHA256: ${{ steps.dependency.outputs.sha256 }} URI: ${{ steps.dependency.outputs.uri }} VERSION: ${{ steps.dependency.outputs.version }} VERSION_PATTERN: '[\d]+\.[\d]+\.[\d]+' - uses: peter-evans/create-pull-request@v3 with: - body: Bumps `google-stackdriver-profiler-nodejs` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. - branch: update/buildpack/google-stackdriver-profiler-nodejs + body: Bumps `google-cloud-profiler-nodejs` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. + branch: update/buildpack/google-cloud-profiler-nodejs commit-message: |- - Bump google-stackdriver-profiler-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} + Bump google-cloud-profiler-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} - Bumps google-stackdriver-profiler-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. + Bumps google-cloud-profiler-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. delete-branch: true labels: semver:minor, type:dependency-upgrade signoff: true - title: Bump google-stackdriver-profiler-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} + title: Bump google-cloud-profiler-nodejs from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} token: ${{ secrets.JAVA_GITHUB_TOKEN }} diff --git a/NOTICE b/NOTICE index dc596e9..a46c448 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,4 @@ -google-stackdriver +google-cloud Copyright (c) 2020-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. diff --git a/README.md b/README.md index 24ca96c..7cfe858 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,44 @@ -# `gcr.io/paketo-buildpacks/google-stackdriver` -The Paketo Google Stackdriver Buildpack is a Cloud Native Buildpack that contributes Stackdriver agents and configures them to connect to the service. +# `paketo-buildpacks/google-cloud` +The Paketo Google Cloud Buildpack is a Cloud Native Buildpack that contributes Google Cloud agents and configures them to connect to their services. ## Behavior -This buildpack will participate if any of the following conditions are met -* A binding exists with `type` of `StackdriverDebugger` -* A binding exists with `type` of `StackdriverProfiler` +* If `$BP_GOOGLE_CLOUD_DEBUGGER_ENABLED` is set to `true` and the application is Java + * At build time, contributes an agent to a layer + * At launch time, if credentials are available, configures the application to use the agent +* If `$BP_GOOGLE_CLOUD_DEBUGGER_ENABLED` is set to `true` and the application is NodeJS + * At build time, contributes an agent to a layer + * At launch time, if credentials are available, configures `$NODE_MODULES` with the agent path. If the main module does not already require `@google-cloud/debug-agent`, prepends the main module with `require('@google-cloud/debug-agent').start({...});`. -The buildpack will do the following for Java applications: +* If `$BP_GOOGLE_CLOUD_PROFILER_ENABLED` is set to `true` and the application is Java + * At build time, contributes an agent to a layer + * At launch time, if credentials are available, configures the application to use the agent +* If `$BP_GOOGLE_CLOUD_PROFILER_ENABLED` is set to `true` and the application is NodeJS + * At build time, contributes an agent to a layer + * At launch time, if credentials are available, configures `$NODE_MODULES` with the agent path. If the main module does not already require `@google-cloud/profiler`, prepends the main module with `require('@google-cloud/profiler').start({...});`. -* If `StackdriverDebugger` binding exists contributes a Java debugger agent to a layer and configures `$JAVA_TOOL_OPTIONS` to use it -* If `StackdriverProfiler` binding exists contributes a Java profiler agent to a layer and configures `$JAVA_TOOL_OPTIONS` to use it -* Sets `$GOOGLE_APPLICATION_CREDENTIALS` to the path of the `ApplicationCredentials` secret +### Credential Availability +If the applications runs within Google Cloud and the [Google Metadata Service][m] is accessible, those credentials will be used. If the application runs within any other environment, credentials must be provided with a service binding as described below. -The buildpack will do the following for NodeJS applications: - -* If `StackdriverDebugger` binding exists - * Contributes a NodeJS debugger agent to a layer and configures `$NODE_MODULES` to use it - * If main module does not already require `@google-cloud/debug-agent` module, prepends the main module with `require('@google-cloud/debug-agent').start();` -* If `StackdriverProfiler` binding exists - * Contributes a NodeJS profiler agent to a layer and configures `$NODE_MODULES` to use it - * If main module does not already require `@google-cloud/profiler` module, prepends the main module with `require('@google-cloud/profiler').start();` -* Sets `$GOOGLE_APPLICATION_CREDENTIALS` to the path of the `ApplicationCredentials` secret +[m]: https://cloud.google.com/compute/docs/storing-retrieving-metadata ## Configuration | Environment Variable | Description | -------------------- | ----------- -| `$BPL_GOOGLE_STACKDRIVER_MODULE` | Configure the name of the application. Defaults to `default-module`. -| `$BPL_GOOGLE_STACKDRIVER_VERSION` | Configure the project id for the application. Defaults to ``. -| `$BPL_GOOGLE_STACKDRIVER_VERSION` | Configure the version of the application. Defaults to ``. +| `$BP_GOOGLE_CLOUD_DEBUGGER_ENABLED` | Whether to add Google Cloud Debugger during build +| `$BP_GOOGLE_CLOUD_PROFILER_ENABLED` | Whether to add Google Cloud Profiler during build +| `$BPL_GOOGLE_CLOUD_MODULE` | Configure the name of the application (required) +| `$BPL_GOOGLE_CLOUD_PROJECT_ID` | Configure the project id for the application (required if running outside of Google Cloud) +| `$BPL_GOOGLE_CLOUD_VERSION` | Configure the version of the application (required) ## Bindings The buildpack optionally accepts the following bindings: +### Type: `GoogleCloud` +|Key | Value | Description +|-------------------------|------------------|------------ +|`ApplicationCredentials` | `` | Google Cloud Application Credentials in JSON form + ### Type: `dependency-mapping` |Key | Value | Description |----------------------|---------|------------ diff --git a/stackdriver/build.go b/build.go similarity index 58% rename from stackdriver/build.go rename to build.go index d8e449e..d310128 100644 --- a/stackdriver/build.go +++ b/build.go @@ -14,12 +14,16 @@ * limitations under the License. */ -package stackdriver +package google import ( "fmt" "github.com/buildpacks/libcnb" + + "github.com/paketo-buildpacks/google-cloud/debugger" + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/google-cloud/profiler" "github.com/paketo-buildpacks/libpak" "github.com/paketo-buildpacks/libpak/bard" ) @@ -50,62 +54,72 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { } dc.Logger = b.Logger - names := []string{"credentials"} + var names []string + + if _, ok, err := pr.Resolve(common.Credentials); err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to resolve %s plan entry\n%w", common.Credentials, err) + } else if ok { + names = append(names, common.Credentials) + } - if _, ok, err := pr.Resolve("google-stackdriver-debugger-java"); err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to resolve google-stackdriver-debugger-java plan entry\n%w", err) + if _, ok, err := pr.Resolve(common.DebuggerJava); err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to resolve %s plan entry\n%w", common.DebuggerJava, err) } else if ok { - dep, err := dr.Resolve("google-stackdriver-debugger-java", "") + dep, err := dr.Resolve(common.DebuggerJava, "") if err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) } - ja := NewJavaDebuggerAgent(dep, dc, result.Plan) + ja := debugger.NewJavaBuild(dep, dc, result.Plan) ja.Logger = b.Logger result.Layers = append(result.Layers, ja) - names = append(names, "java-debugger") + names = append(names, common.DebuggerJava) } - if _, ok, err := pr.Resolve("google-stackdriver-debugger-nodejs"); err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to resolve google-stackdriver-debugger-nodejs plan entry\n%w", err) + if _, ok, err := pr.Resolve(common.DebuggerNodeJS); err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to resolve %s plan entry\n%w", common.DebuggerNodeJS, err) } else if ok { - dep, err := dr.Resolve("google-stackdriver-debugger-nodejs", "") + dep, err := dr.Resolve(common.DebuggerNodeJS, "") if err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) } - ja := NewNodeJSDebuggerAgent(context.Buildpack.Path, dep, dc, result.Plan) + ja := debugger.NewNodeJSBuild(dep, dc, result.Plan) ja.Logger = b.Logger result.Layers = append(result.Layers, ja) + + names = append(names, common.DebuggerNodeJS) } - if _, ok, err := pr.Resolve("google-stackdriver-profiler-java"); err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to resolve google-stackdriver-profiler-java plan entry\n%w", err) + if _, ok, err := pr.Resolve(common.ProfilerJava); err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to resolve %s plan entry\n%w", common.ProfilerJava, err) } else if ok { - dep, err := dr.Resolve("google-stackdriver-profiler-java", "") + dep, err := dr.Resolve(common.ProfilerJava, "") if err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) } - ja := NewJavaProfilerAgent(dep, dc, result.Plan) + ja := profiler.NewJavaBuild(dep, dc, result.Plan) ja.Logger = b.Logger result.Layers = append(result.Layers, ja) - names = append(names, "java-profiler") + names = append(names, common.ProfilerJava) } - if _, ok, err := pr.Resolve("google-stackdriver-profiler-nodejs"); err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to resolve google-stackdriver-profiler-nodejs plan entry\n%w", err) + if _, ok, err := pr.Resolve(common.ProfilerNodeJS); err != nil { + return libcnb.BuildResult{}, fmt.Errorf("unable to resolve %s plan entry\n%w", common.ProfilerNodeJS, err) } else if ok { - dep, err := dr.Resolve("google-stackdriver-profiler-nodejs", "") + dep, err := dr.Resolve(common.ProfilerNodeJS, "") if err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) } - ja := NewNodeJSProfilerAgent(context.Buildpack.Path, dep, dc, result.Plan) + ja := profiler.NewNodeJSBuild(dep, dc, result.Plan) ja.Logger = b.Logger result.Layers = append(result.Layers, ja) + + names = append(names, common.ProfilerNodeJS) } h := libpak.NewHelperLayerContributor(context.Buildpack, result.Plan, names...) diff --git a/stackdriver/build_test.go b/build_test.go similarity index 63% rename from stackdriver/build_test.go rename to build_test.go index c5d236c..8941ea4 100644 --- a/stackdriver/build_test.go +++ b/build_test.go @@ -14,17 +14,19 @@ * limitations under the License. */ -package stackdriver_test +package google_test import ( "testing" "github.com/buildpacks/libcnb" . "github.com/onsi/gomega" - "github.com/paketo-buildpacks/libpak" "github.com/sclevine/spec" - "github.com/paketo-buildpacks/google-stackdriver/stackdriver" + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/libpak" + + "github.com/paketo-buildpacks/google-cloud" ) func testBuild(t *testing.T, context spec.G, it spec.S) { @@ -34,12 +36,23 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { ctx libcnb.BuildContext ) - it("contributes Java debugger agent", func() { - ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: "google-stackdriver-debugger-java"}) + it("contributes credentials", func() { + ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: common.Credentials}) + + result, err := google.Build{}.Build(ctx) + Expect(err).NotTo(HaveOccurred()) + + Expect(result.Layers).To(HaveLen(1)) + Expect(result.Layers[0].Name()).To(Equal("helper")) + Expect(result.Layers[0].(libpak.HelperLayerContributor).Names).To(Equal([]string{common.Credentials})) + }) + + it("contributes debugger Java", func() { + ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: common.DebuggerJava}) ctx.Buildpack.Metadata = map[string]interface{}{ "dependencies": []map[string]interface{}{ { - "id": "google-stackdriver-debugger-java", + "id": common.DebuggerJava, "version": "1.1.1", "stacks": []interface{}{"test-stack-id"}, }, @@ -47,21 +60,21 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { } ctx.StackID = "test-stack-id" - result, err := stackdriver.Build{}.Build(ctx) + result, err := google.Build{}.Build(ctx) Expect(err).NotTo(HaveOccurred()) Expect(result.Layers).To(HaveLen(2)) - Expect(result.Layers[0].Name()).To(Equal("google-stackdriver-debugger-java")) + Expect(result.Layers[0].Name()).To(Equal(common.DebuggerJava)) Expect(result.Layers[1].Name()).To(Equal("helper")) - Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{"credentials", "java-debugger"})) + Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{common.DebuggerJava})) }) - it("contributes NodeJS debugger agent", func() { - ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: "google-stackdriver-debugger-nodejs"}) + it("contributes debugger NodeJS", func() { + ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: common.DebuggerNodeJS}) ctx.Buildpack.Metadata = map[string]interface{}{ "dependencies": []map[string]interface{}{ { - "id": "google-stackdriver-debugger-nodejs", + "id": common.DebuggerNodeJS, "version": "1.1.1", "stacks": []interface{}{"test-stack-id"}, }, @@ -69,21 +82,21 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { } ctx.StackID = "test-stack-id" - result, err := stackdriver.Build{}.Build(ctx) + result, err := google.Build{}.Build(ctx) Expect(err).NotTo(HaveOccurred()) Expect(result.Layers).To(HaveLen(2)) - Expect(result.Layers[0].Name()).To(Equal("google-stackdriver-debugger-nodejs")) + Expect(result.Layers[0].Name()).To(Equal(common.DebuggerNodeJS)) Expect(result.Layers[1].Name()).To(Equal("helper")) - Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{"credentials"})) + Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{common.DebuggerNodeJS})) }) - it("contributes Java profiler agent", func() { - ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: "google-stackdriver-profiler-java"}) + it("contributes profiler Java", func() { + ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: common.ProfilerJava}) ctx.Buildpack.Metadata = map[string]interface{}{ "dependencies": []map[string]interface{}{ { - "id": "google-stackdriver-profiler-java", + "id": common.ProfilerJava, "version": "1.1.1", "stacks": []interface{}{"test-stack-id"}, }, @@ -91,21 +104,21 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { } ctx.StackID = "test-stack-id" - result, err := stackdriver.Build{}.Build(ctx) + result, err := google.Build{}.Build(ctx) Expect(err).NotTo(HaveOccurred()) Expect(result.Layers).To(HaveLen(2)) - Expect(result.Layers[0].Name()).To(Equal("google-stackdriver-profiler-java")) + Expect(result.Layers[0].Name()).To(Equal(common.ProfilerJava)) Expect(result.Layers[1].Name()).To(Equal("helper")) - Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{"credentials", "java-profiler"})) + Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{common.ProfilerJava})) }) - it("contributes NodeJS profiler agent", func() { - ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: "google-stackdriver-profiler-nodejs"}) + it("contributes profiler NodeJS", func() { + ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: common.ProfilerNodeJS}) ctx.Buildpack.Metadata = map[string]interface{}{ "dependencies": []map[string]interface{}{ { - "id": "google-stackdriver-profiler-nodejs", + "id": common.ProfilerNodeJS, "version": "1.1.1", "stacks": []interface{}{"test-stack-id"}, }, @@ -113,12 +126,12 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { } ctx.StackID = "test-stack-id" - result, err := stackdriver.Build{}.Build(ctx) + result, err := google.Build{}.Build(ctx) Expect(err).NotTo(HaveOccurred()) Expect(result.Layers).To(HaveLen(2)) - Expect(result.Layers[0].Name()).To(Equal("google-stackdriver-profiler-nodejs")) + Expect(result.Layers[0].Name()).To(Equal(common.ProfilerNodeJS)) Expect(result.Layers[1].Name()).To(Equal("helper")) - Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{"credentials"})) + Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{common.ProfilerNodeJS})) }) } diff --git a/buildpack.toml b/buildpack.toml index 638017f..de42ab0 100644 --- a/buildpack.toml +++ b/buildpack.toml @@ -15,10 +15,10 @@ api = "0.4" [buildpack] -id = "paketo-buildpacks/google-stackdriver" -name = "Paketo Google Stackdriver Buildpack" +id = "paketo-buildpacks/google-cloud" +name = "Paketo Google Cloud Buildpack" version = "{{.version}}" -homepage = "https://github.com/paketo-buildpacks/google-stackdriver" +homepage = "https://github.com/paketo-buildpacks/google-cloud" [[stacks]] id = "io.buildpacks.stacks.bionic" @@ -27,24 +27,33 @@ id = "io.buildpacks.stacks.bionic" id = "org.cloudfoundry.stacks.cflinuxfs3" [[metadata.configurations]] -name = "BPL_GOOGLE_STACKDRIVER_MODULE" +name = "BP_GOOGLE_CLOUD_DEBUGGER_ENABLED" +description = "whether to contribute Google Cloud Debugger support" +build = true + +[[metadata.configurations]] +name = "BP_GOOGLE_CLOUD_PROFILER_ENABLED" +description = "whether to contribute Google Cloud Profiler support" +build = true + +[[metadata.configurations]] +name = "BPL_GOOGLE_CLOUD_MODULE" description = "the name of the application" -default = "default-module" launch = true [[metadata.configurations]] -name = "BPL_GOOGLE_STACKDRIVER_PROJECT_ID" +name = "BPL_GOOGLE_CLOUD_PROJECT_ID" description = "the project id for the application" launch = true [[metadata.configurations]] -name = "BPL_GOOGLE_STACKDRIVER_VERSION" +name = "BPL_GOOGLE_CLOUD_VERSION" description = "the version of the application" launch = true [[metadata.dependencies]] -id = "google-stackdriver-debugger-java" -name = "Google Stackdriver Debugger Java Agent" +id = "google-cloud-debugger-java" +name = "Google Cloud Debugger Java Agent" version = "2.26.0" uri = "https://github.com/GoogleCloudPlatform/cloud-debug-java/releases/download/v2.26/compute-java_debian-wheezy_cdbg_java_agent_gce.tar" sha256 = "4fe2c6a55aa46805385817b4727d19cddf4cf9091348240fa89fedbbecd02211" @@ -55,8 +64,8 @@ stacks = [ "io.buildpacks.stacks.bionic", "org.cloudfoundry.stacks.cflinuxfs3" uri = "https://github.com/GoogleCloudPlatform/cloud-debug-java/blob/master/LICENSE" [[metadata.dependencies]] -id = "google-stackdriver-debugger-nodejs" -name = "Google Stackdriver Debugger NodeJS Agent" +id = "google-cloud-debugger-nodejs" +name = "Google Cloud Debugger NodeJS Agent" version = "5.1.3" uri = "https://registry.npmjs.org/@google-cloud/debug-agent/-/debug-agent-5.1.3.tgz" sha256 = "d6bfb81f34e264117585b0192c0378e26f6624fb1ca0578eb42576cbd0e537b2" @@ -67,8 +76,8 @@ stacks = [ "io.buildpacks.stacks.bionic", "org.cloudfoundry.stacks.cflinuxfs3" uri = "https://github.com/googleapis/cloud-debug-nodejs/blob/master/LICENSE" [[metadata.dependencies]] -id = "google-stackdriver-profiler-java" -name = "Google Stackdriver Profiler Java Agent" +id = "google-cloud-profiler-java" +name = "Google Cloud Profiler Java Agent" version = "20201116.0.0" uri = "https://storage.googleapis.com/cloud-profiler/java/cloud-profiler-java-agent_20201116_RC00.tar.gz" sha256 = "134a75b6620e136dd53b96e996e466fa86ffc891d46f90b97f4d68fab1cbf9ea" @@ -79,8 +88,8 @@ stacks = [ "io.buildpacks.stacks.bionic", "org.cloudfoundry.stacks.cflinuxfs3" uri = "https://github.com/GoogleCloudPlatform/cloud-profiler-java/blob/master/LICENSE" [[metadata.dependencies]] -id = "google-stackdriver-profiler-nodejs" -name = "Google Stackdriver Profiler NodeJS Agent" +id = "google-cloud-profiler-nodejs" +name = "Google Cloud Profiler NodeJS Agent" version = "4.1.0" uri = "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-4.1.0.tgz" sha256 = "6f80f3a043dcbc79c6bfacc9ef2e3ae1980f77ad0c64bd96c90c98ec7db61f8d" diff --git a/cmd/helper/main.go b/cmd/helper/main.go index b04e90b..26c54bf 100644 --- a/cmd/helper/main.go +++ b/cmd/helper/main.go @@ -18,34 +18,82 @@ package main import ( "fmt" + "net/http" + "net/url" "os" "github.com/buildpacks/libcnb" + + "github.com/paketo-buildpacks/google-cloud/credentials" + "github.com/paketo-buildpacks/google-cloud/debugger" + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/google-cloud/profiler" "github.com/paketo-buildpacks/libpak/bard" + "github.com/paketo-buildpacks/libpak/bindings" "github.com/paketo-buildpacks/libpak/sherpa" - - "github.com/paketo-buildpacks/google-stackdriver/helper" ) func main() { sherpa.Execute(func() error { - var ( - err error - l = bard.NewLogger(os.Stdout) - c = helper.Credentials{Logger: l} - d = helper.JavaDebugger{Logger: l} - p = helper.JavaProfiler{Logger: l} - ) + l := bard.NewLogger(os.Stdout) - c.Bindings, err = libcnb.NewBindingsFromEnvironment() + a, err := os.Getwd() + if err != nil { + return fmt.Errorf("unable to read working directory\n%w", err) + } + + bs, err := libcnb.NewBindingsFromEnvironment() if err != nil { return fmt.Errorf("unable to read bindings from environment\n%w", err) } + var ( + b libcnb.Binding + c common.CredentialSource + ) + + if hasMetadataServer() { + c = common.MetadataServer + } else if g, ok, err := bindings.ResolveOne(bs, bindings.OfType("GoogleCloud")); err != nil { + return fmt.Errorf("unable to resolve GoogleCloud binding\n%w", err) + } else if ok { + b = g + c = common.Binding + } else { + c = common.None + } + return sherpa.Helpers(map[string]sherpa.ExecD{ - "credentials": c, - "java-debugger": d, - "java-profiler": p, + common.Credentials: credentials.Launch{Binding: b, CredentialSource: c, Logger: l}, + common.DebuggerJava: debugger.JavaLaunch{CredentialSource: c, Logger: l}, + common.DebuggerNodeJS: debugger.NodeJSLaunch{ApplicationPath: a, CredentialSource: c, Logger: l}, + common.ProfilerJava: profiler.JavaLaunch{CredentialSource: c, Logger: l}, + common.ProfilerNodeJS: profiler.NodeJSLaunch{ApplicationPath: a, CredentialSource: c, Logger: l}, }) }) } + +// hasMetadataServer detects whether the application has access to a Google Cloud metadata server. Detection is based +// on the existence of the metadata server as defined in +// https://cloud.google.com/compute/docs/storing-retrieving-metadata#querying. +func hasMetadataServer() bool { + client := &http.Client{ + Transport: &http.Transport{ + Proxy: func(req *http.Request) (*url.URL, error) { return nil, nil }, + }, + } + + req, err := http.NewRequest("GET", "http://metadata.google.internal/computeMetadata/v1/", nil) + if err != nil { + return false + } + req.Header.Add("Metadata-Flavor", "Google") + + resp, err := client.Do(req) + if err != nil { + return false + } + defer resp.Body.Close() + + return resp.StatusCode == 200 +} diff --git a/cmd/main/main.go b/cmd/main/main.go index 2535a0a..0971bc6 100644 --- a/cmd/main/main.go +++ b/cmd/main/main.go @@ -22,12 +22,12 @@ import ( "github.com/paketo-buildpacks/libpak" "github.com/paketo-buildpacks/libpak/bard" - "github.com/paketo-buildpacks/google-stackdriver/stackdriver" + "github.com/paketo-buildpacks/google-cloud" ) func main() { libpak.Main( - stackdriver.Detect{}, - stackdriver.Build{Logger: bard.NewLogger(os.Stdout)}, + google.Detect{}, + google.Build{Logger: bard.NewLogger(os.Stdout)}, ) } diff --git a/credentials/credentials.go b/credentials/credentials.go new file mode 100644 index 0000000..67a590e --- /dev/null +++ b/credentials/credentials.go @@ -0,0 +1,43 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package credentials + +import ( + "github.com/buildpacks/libcnb" + + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/libpak/bard" +) + +type Launch struct { + Binding libcnb.Binding + CredentialSource common.CredentialSource + Logger bard.Logger +} + +func (l Launch) Execute() (map[string]string, error) { + if l.CredentialSource == common.MetadataServer || l.CredentialSource == common.None { + return nil, nil + } + + if p, ok := l.Binding.SecretFilePath("ApplicationCredentials"); ok { + l.Logger.Info("Configuring Google Cloud application credentials") + return map[string]string{"GOOGLE_APPLICATION_CREDENTIALS": p}, nil + } + + return nil, nil +} diff --git a/credentials/credentials_test.go b/credentials/credentials_test.go new file mode 100644 index 0000000..ee56d7d --- /dev/null +++ b/credentials/credentials_test.go @@ -0,0 +1,74 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package credentials_test + +import ( + "testing" + + "github.com/buildpacks/libcnb" + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + + "github.com/paketo-buildpacks/google-cloud/credentials" + "github.com/paketo-buildpacks/google-cloud/internal/common" +) + +func testCredentials(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + ) + + context("launch", func() { + var ( + l credentials.Launch + ) + + it("does not contribute if source is MetadataServer", func() { + l.CredentialSource = common.MetadataServer + + Expect(l.Execute()).To(BeNil()) + }) + + it("does not contribute if source is None", func() { + l.CredentialSource = common.None + + Expect(l.Execute()).To(BeNil()) + }) + + it("does not contribute if ApplicationCredentials does not exist", func() { + l.Binding = libcnb.Binding{ + Path: "/test/path/test-binding", + Secret: map[string]string{}, + } + l.CredentialSource = common.Binding + + Expect(l.Execute()).To(BeNil()) + }) + + it("contributes credentials if ApplicationCredentials exists", func() { + l.Binding = libcnb.Binding{ + Path: "/test/path/test-binding", + Secret: map[string]string{"ApplicationCredentials": "test-value"}, + } + l.CredentialSource = common.Binding + + Expect(l.Execute()).To(Equal(map[string]string{ + "GOOGLE_APPLICATION_CREDENTIALS": "/test/path/test-binding/ApplicationCredentials", + })) + }) + }) +} diff --git a/helper/init_test.go b/credentials/init_test.go similarity index 83% rename from helper/init_test.go rename to credentials/init_test.go index 6c140da..0e3afd3 100644 --- a/helper/init_test.go +++ b/credentials/init_test.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package helper_test +package credentials_test import ( "testing" @@ -24,9 +24,7 @@ import ( ) func TestUnit(t *testing.T) { - suite := spec.New("helper", spec.Report(report.Terminal{})) + suite := spec.New("credentials", spec.Report(report.Terminal{})) suite("Credentials", testCredentials) - suite("JavaDebugger", testJavaDebugger) - suite("JavaProfiler", testJavaProfiler) suite.Run(t) } diff --git a/debugger/init_test.go b/debugger/init_test.go new file mode 100644 index 0000000..fba0ccb --- /dev/null +++ b/debugger/init_test.go @@ -0,0 +1,31 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package debugger_test + +import ( + "testing" + + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestUnit(t *testing.T) { + suite := spec.New("debugger", spec.Report(report.Terminal{})) + suite("Java", testJava) + suite("NodeJS", testNodeJS) + suite.Run(t) +} diff --git a/debugger/java.go b/debugger/java.go new file mode 100644 index 0000000..9c72207 --- /dev/null +++ b/debugger/java.go @@ -0,0 +1,108 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package debugger + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/buildpacks/libcnb" + + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/libpak" + "github.com/paketo-buildpacks/libpak/bard" + "github.com/paketo-buildpacks/libpak/crush" + "github.com/paketo-buildpacks/libpak/sherpa" +) + +const AgentPath = "BPI_GOOGLE_CLOUD_DEBUGGER_AGENT_PATH" + +type JavaBuild struct { + LayerContributor libpak.DependencyLayerContributor + Logger bard.Logger +} + +func NewJavaBuild(dependency libpak.BuildpackDependency, cache libpak.DependencyCache, plan *libcnb.BuildpackPlan) JavaBuild { + return JavaBuild{LayerContributor: libpak.NewDependencyLayerContributor(dependency, cache, plan)} +} + +func (j JavaBuild) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { + j.LayerContributor.Logger = j.Logger + + return j.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { + j.Logger.Bodyf("Expanding to %s", layer.Path) + + if err := crush.ExtractTarGz(artifact, layer.Path, 0); err != nil { + return libcnb.Layer{}, fmt.Errorf("unable to extract %s\n%w", artifact.Name(), err) + } + + layer.LaunchEnvironment.Default(AgentPath, filepath.Join(layer.Path, "cdbg_java_agent.so")) + + return layer, nil + }, libpak.LaunchLayer) +} + +func (j JavaBuild) Name() string { + return j.LayerContributor.LayerName() +} + +type JavaLaunch struct { + CredentialSource common.CredentialSource + Logger bard.Logger +} + +// https://cloud.google.com/debugger/docs/setup/java#gke +// https://cloud.google.com/debugger/docs/setup/java#local +func (j JavaLaunch) Execute() (map[string]string, error) { + if j.CredentialSource == common.None { + j.Logger.Info("Google Cloud Debugger disabled") + return nil, nil + } + + p, err := sherpa.GetEnvRequired(AgentPath) + if err != nil { + return nil, err + } + + m, err := sherpa.GetEnvRequired(common.Module) + if err != nil { + return nil, err + } + + v, err := sherpa.GetEnvRequired(common.Version) + if err != nil { + return nil, err + } + + j.Logger.Infof("Google Cloud Debugger enabled (%s:%s)", m, v) + + var agent []string + agent = append(agent, + fmt.Sprintf("-agentpath:%s=--logtostderr=1", p), + fmt.Sprintf("-Dcom.google.cdbg.module=%s", m), + fmt.Sprintf("-Dcom.google.cdbg.version=%s", v), + ) + + if j.CredentialSource == common.Binding { + agent = append(agent, "-Dcom.google.cdbg.auth.serviceaccount.enable=true") + } + + return map[string]string{ + "JAVA_TOOL_OPTIONS": sherpa.AppendToEnvVar("JAVA_TOOL_OPTIONS", " ", agent...), + }, nil +} diff --git a/debugger/java_test.go b/debugger/java_test.go new file mode 100644 index 0000000..5290918 --- /dev/null +++ b/debugger/java_test.go @@ -0,0 +1,178 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package debugger_test + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/buildpacks/libcnb" + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + + "github.com/paketo-buildpacks/google-cloud/debugger" + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/libpak" +) + +func testJava(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + ) + + context("Build", func() { + var ( + ctx libcnb.BuildContext + ) + + it.Before(func() { + var err error + ctx.Layers.Path, err = ioutil.TempDir("", "debugger-java-build-layers") + Expect(err).NotTo(HaveOccurred()) + }) + + it.After(func() { + Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) + }) + + it("contributes Java agent", func() { + dep := libpak.BuildpackDependency{ + URI: "https://localhost/stub-cloud-debugger-agent.tar.gz", + SHA256: "80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab", + } + dc := libpak.DependencyCache{CachePath: "testdata"} + + layer, err := ctx.Layers.Layer("test-layer") + Expect(err).NotTo(HaveOccurred()) + + layer, err = debugger.NewJavaBuild(dep, dc, &libcnb.BuildpackPlan{}).Contribute(layer) + Expect(err).NotTo(HaveOccurred()) + + Expect(layer.Launch).To(BeTrue()) + + file := filepath.Join(layer.Path, "cdbg_java_agent.so") + Expect(file).To(BeARegularFile()) + Expect(layer.LaunchEnvironment[fmt.Sprintf("%s.default", debugger.AgentPath)]).To(Equal(file)) + }) + + }) + + context("Launch", func() { + var ( + l = debugger.JavaLaunch{ + CredentialSource: common.MetadataServer, + } + ) + + it.Before(func() { + Expect(os.Setenv(debugger.AgentPath, "test-path")).To(Succeed()) + Expect(os.Setenv(common.Module, "test-module")).To(Succeed()) + Expect(os.Setenv(common.Version, "test-version")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv(debugger.AgentPath)).To(Succeed()) + Expect(os.Unsetenv(common.Module)).To(Succeed()) + Expect(os.Unsetenv(common.Version)).To(Succeed()) + }) + + it("does not contribute if source is None", func() { + l.CredentialSource = common.None + + Expect(l.Execute()).To(BeNil()) + }) + + it("returns error if BPI_GOOGLE_CLOUD_DEBUGGER_AGENT_PATH is not set", func() { + Expect(os.Unsetenv(debugger.AgentPath)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPI_GOOGLE_CLOUD_DEBUGGER_AGENT_PATH must be set")) + }) + + it("returns error if BPL_GOOGLE_CLOUD_MODULE is not set", func() { + Expect(os.Unsetenv(common.Module)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPL_GOOGLE_CLOUD_MODULE must be set")) + }) + + it("returns error if BPL_GOOGLE_CLOUD_VERSION is not set", func() { + Expect(os.Unsetenv(common.Version)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPL_GOOGLE_CLOUD_VERSION must be set")) + }) + + context("binding", func() { + + it.Before(func() { + l.CredentialSource = common.Binding + }) + + it("contributes JAVA_TOOL_OPTIONS", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "JAVA_TOOL_OPTIONS": strings.Join([]string{ + "-agentpath:test-path=--logtostderr=1", + "-Dcom.google.cdbg.module=test-module", + "-Dcom.google.cdbg.version=test-version", + "-Dcom.google.cdbg.auth.serviceaccount.enable=true", + }, " "), + })) + + }) + }) + + context("metadata server", func() { + + it("contributes JAVA_TOOL_OPTIONS", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "JAVA_TOOL_OPTIONS": strings.Join([]string{ + "-agentpath:test-path=--logtostderr=1", + "-Dcom.google.cdbg.module=test-module", + "-Dcom.google.cdbg.version=test-version", + }, " "), + })) + }) + }) + + context("existing $JAVA_TOOL_OPTIONS", func() { + + it.Before(func() { + Expect(os.Setenv("JAVA_TOOL_OPTIONS", "test-java-tool-options")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("JAVA_TOOL_OPTIONS")).To(Succeed()) + }) + + it("contributes JAVA_TOOL_OPTIONS", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "JAVA_TOOL_OPTIONS": strings.Join([]string{ + "test-java-tool-options", + "-agentpath:test-path=--logtostderr=1", + "-Dcom.google.cdbg.module=test-module", + "-Dcom.google.cdbg.version=test-version", + }, " "), + })) + }) + }) + }) +} diff --git a/debugger/nodejs.go b/debugger/nodejs.go new file mode 100644 index 0000000..6ebbd9c --- /dev/null +++ b/debugger/nodejs.go @@ -0,0 +1,158 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package debugger + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/buildpacks/libcnb" + + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/google-cloud/internal/nodejs" + "github.com/paketo-buildpacks/libpak" + "github.com/paketo-buildpacks/libpak/bard" + "github.com/paketo-buildpacks/libpak/effect" + "github.com/paketo-buildpacks/libpak/sherpa" +) + +const ( + NodeModule = "@google-cloud/debug-agent" + NodePath = "BPI_GOOGLE_CLOUD_DEBUGGER_NODE_PATH" +) + +type NodeJSBuild struct { + Executor effect.Executor + LayerContributor libpak.DependencyLayerContributor + Logger bard.Logger +} + +func NewNodeJSBuild(dependency libpak.BuildpackDependency, cache libpak.DependencyCache, plan *libcnb.BuildpackPlan) NodeJSBuild { + return NodeJSBuild{ + Executor: effect.NewExecutor(), + LayerContributor: libpak.NewDependencyLayerContributor(dependency, cache, plan), + } +} + +func (n NodeJSBuild) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { + n.LayerContributor.Logger = n.Logger + + return n.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { + n.Logger.Bodyf("Installing to %s", layer.Path) + + if err := n.Executor.Execute(effect.Execution{ + Command: "npm", + Args: []string{"install", "--no-save", artifact.Name()}, + Dir: layer.Path, + Stdout: n.Logger.InfoWriter(), + Stderr: n.Logger.InfoWriter(), + }); err != nil { + return libcnb.Layer{}, fmt.Errorf("unable to run npm install\n%w", err) + } + + layer.LaunchEnvironment.Default(NodePath, filepath.Join(layer.Path, "node_modules")) + + return layer, nil + }, libpak.LaunchLayer) +} + +func (n NodeJSBuild) Name() string { + return n.LayerContributor.LayerName() +} + +type NodeJSLaunch struct { + ApplicationPath string + CredentialSource common.CredentialSource + Logger bard.Logger +} + +// https://cloud.google.com/debugger/docs/setup/nodejs#gke +// https://cloud.google.com/debugger/docs/setup/nodejs#local +func (n NodeJSLaunch) Execute() (map[string]string, error) { + if n.CredentialSource == common.None { + n.Logger.Info("Google Cloud Debugger disabled") + return nil, nil + } + + p, err := sherpa.GetEnvRequired(NodePath) + if err != nil { + return nil, err + } + + s, err := sherpa.GetEnvRequired(common.Module) + if err != nil { + return nil, err + } + + v, err := sherpa.GetEnvRequired(common.Version) + if err != nil { + return nil, err + } + + n.Logger.Infof("Google Cloud Debugger enabled (%s:%s)", s, v) + + mod, err := sherpa.NodeJSMainModule(n.ApplicationPath) + if err != nil { + return nil, fmt.Errorf("unable to find main module in %s\n%w", n.ApplicationPath, err) + } + + file := filepath.Join(n.ApplicationPath, mod) + c, err := ioutil.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("unable to read contents of %s\n%w", file, err) + } + + e, err := nodejs.IsModuleRequired(NodeModule, c) + if err != nil { + return nil, fmt.Errorf("unable to determine if %s is already required\n%w", NodeModule, err) + } + + if !e { + n.Logger.Headerf("Requiring '%s' module", NodeModule) + + mc := nodejs.ModuleContext{ + Module: NodeModule, + Service: s, + Version: v, + } + + var b []byte + if n.CredentialSource == common.Binding { + mc.ProjectId, err = sherpa.GetEnvRequired(common.ProjectID) + if err != nil { + return nil, err + } + + b, err = nodejs.RequireModuleExternal(mc) + } else { + b, err = nodejs.RequireModule(mc) + } + if err != nil { + return nil, fmt.Errorf("unable to create require prologue\n%w", err) + } + + if err := ioutil.WriteFile(file, append(b, c...), 0644); err != nil { + return nil, fmt.Errorf("unable to write main module %s\n%w", file, err) + } + } + + return map[string]string{ + "NODE_PATH": sherpa.AppendToEnvVar("NODE_PATH", string(os.PathListSeparator), p), + }, nil +} diff --git a/debugger/nodejs_test.go b/debugger/nodejs_test.go new file mode 100644 index 0000000..2f15417 --- /dev/null +++ b/debugger/nodejs_test.go @@ -0,0 +1,287 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package debugger_test + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/buildpacks/libcnb" + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + "github.com/stretchr/testify/mock" + + "github.com/paketo-buildpacks/google-cloud/debugger" + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/libpak" + "github.com/paketo-buildpacks/libpak/effect" + "github.com/paketo-buildpacks/libpak/effect/mocks" +) + +func testNodeJS(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + ) + + context("Build", func() { + var ( + ctx libcnb.BuildContext + executor *mocks.Executor + ) + + it.Before(func() { + var err error + ctx.Layers.Path, err = ioutil.TempDir("", "debugger-nodejs-build-layers") + Expect(err).NotTo(HaveOccurred()) + + executor = &mocks.Executor{} + executor.On("Execute", mock.Anything).Return(nil) + }) + + it.After(func() { + Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) + }) + + it("contributes NodeJS agent", func() { + dep := libpak.BuildpackDependency{ + URI: "https://localhost/stub-cloud-debugger-agent.tgz", + SHA256: "c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6", + } + dc := libpak.DependencyCache{CachePath: "testdata"} + + n := debugger.NewNodeJSBuild(dep, dc, &libcnb.BuildpackPlan{}) + n.Executor = executor + layer, err := ctx.Layers.Layer("test-layer") + Expect(err).NotTo(HaveOccurred()) + + layer, err = n.Contribute(layer) + Expect(err).NotTo(HaveOccurred()) + + Expect(layer.Launch).To(BeTrue()) + + execution := executor.Calls[0].Arguments[0].(effect.Execution) + Expect(execution.Command).To(Equal("npm")) + Expect(execution.Args).To(Equal([]string{"install", "--no-save", + filepath.Join("testdata", + "c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6", + "stub-cloud-debugger-agent.tgz"), + })) + + Expect(layer.LaunchEnvironment[fmt.Sprintf("%s.default", debugger.NodePath)]). + To(Equal(filepath.Join(layer.Path, "node_modules"))) + }) + }) + + context("Launch", func() { + var ( + l = debugger.NodeJSLaunch{ + CredentialSource: common.MetadataServer, + } + ) + + it.Before(func() { + var err error + l.ApplicationPath, err = ioutil.TempDir("", "debugger-nodejs-launch-application") + Expect(err).NotTo(HaveOccurred()) + + Expect(ioutil.WriteFile(filepath.Join(l.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), 0644)). + To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(l.ApplicationPath, "main.js"), []byte("test"), 0644)). + To(Succeed()) + + Expect(os.Setenv(debugger.NodePath, "test-path")).To(Succeed()) + Expect(os.Setenv(common.Module, "test-module")).To(Succeed()) + Expect(os.Setenv(common.Version, "test-version")).To(Succeed()) + }) + + it.After(func() { + Expect(os.RemoveAll(l.ApplicationPath)).To(Succeed()) + + Expect(os.Unsetenv(debugger.NodePath)).To(Succeed()) + Expect(os.Unsetenv(common.Module)).To(Succeed()) + Expect(os.Unsetenv(common.Version)).To(Succeed()) + }) + + it("does not contribute if source is None", func() { + l.CredentialSource = common.None + + Expect(l.Execute()).To(BeNil()) + }) + + it("returns error if BPI_GOOGLE_CLOUD_DEBUGGER_NODE_PATH is not set", func() { + Expect(os.Unsetenv(debugger.NodePath)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPI_GOOGLE_CLOUD_DEBUGGER_NODE_PATH must be set")) + }) + + it("returns error if BPL_GOOGLE_CLOUD_MODULE is not set", func() { + Expect(os.Unsetenv(common.Module)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPL_GOOGLE_CLOUD_MODULE must be set")) + }) + + it("returns error if BPL_GOOGLE_CLOUD_VERSION is not set", func() { + Expect(os.Unsetenv(common.Version)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPL_GOOGLE_CLOUD_VERSION must be set")) + }) + + context("binding", func() { + + it.Before(func() { + l.CredentialSource = common.Binding + Expect(os.Setenv(common.ProjectID, "test-project-id")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv(common.ProjectID)).To(Succeed()) + }) + + it("returns error if BPL_GOOGLE_CLOUD_PROJECT_ID is not set", func() { + Expect(os.Unsetenv(common.ProjectID)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPL_GOOGLE_CLOUD_PROJECT_ID must be set")) + }) + + context("@google-cloud/debug-agent already required", func() { + + it.Before(func() { + Expect(ioutil.WriteFile(filepath.Join(l.ApplicationPath, "main.js"), []byte(`test +require('@google-cloud/debug-agent').start() +test`), 0644)). + To(Succeed()) + }) + + it("does not contribute require('@google-cloud/debug-agent')", func() { + _, err := l.Execute() + Expect(err).NotTo(HaveOccurred()) + + b, err := ioutil.ReadFile(filepath.Join(l.ApplicationPath, "main.js")) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(b)).To(Equal(`test +require('@google-cloud/debug-agent').start() +test`)) + }) + }) + + context("@google-cloud/debug-agent not already required", func() { + + it("contributes require('@google-cloud/debug-agent')", func() { + _, err := l.Execute() + Expect(err).NotTo(HaveOccurred()) + + b, err := ioutil.ReadFile(filepath.Join(l.ApplicationPath, "main.js")) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(b)).To(Equal(`require('@google-cloud/debug-agent').start({ + projectId: 'test-project-id', + serviceContext: { + service: 'test-module', + version: 'test-version', + }, +}); +test`)) + }) + }) + + it("contributes NODE_PATH", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "NODE_PATH": "test-path", + })) + }) + }) + + context("metadata server", func() { + + context("@google-cloud/debug-agent already required", func() { + + it.Before(func() { + Expect(ioutil.WriteFile(filepath.Join(l.ApplicationPath, "main.js"), []byte(`test +require('@google-cloud/debug-agent').start() +test`), 0644)). + To(Succeed()) + }) + + it("does not contribute require('@google-cloud/debug-agent')", func() { + _, err := l.Execute() + Expect(err).NotTo(HaveOccurred()) + + b, err := ioutil.ReadFile(filepath.Join(l.ApplicationPath, "main.js")) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(b)).To(Equal(`test +require('@google-cloud/debug-agent').start() +test`)) + }) + }) + + context("@google-cloud/debug-agent not already required", func() { + + it("contributes require('@google-cloud/debug-agent')", func() { + _, err := l.Execute() + Expect(err).NotTo(HaveOccurred()) + + b, err := ioutil.ReadFile(filepath.Join(l.ApplicationPath, "main.js")) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(b)).To(Equal(`require('@google-cloud/debug-agent').start({ + serviceContext: { + service: 'test-module', + version: 'test-version', + }, +}); +test`)) + }) + }) + + it("contributes NODE_PATH", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "NODE_PATH": "test-path", + })) + }) + }) + + context("existing $NODE_PATH", func() { + + it.Before(func() { + Expect(os.Setenv("NODE_PATH", "test-node-path")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("NODE_PATH")).To(Succeed()) + }) + + it("contributes NODE_PATH", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "NODE_PATH": strings.Join([]string{ + "test-node-path", + "test-path", + }, string(os.PathListSeparator)), + })) + }) + }) + }) +} diff --git a/stackdriver/testdata/80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab.toml b/debugger/testdata/80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab.toml similarity index 53% rename from stackdriver/testdata/80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab.toml rename to debugger/testdata/80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab.toml index b53e31e..34b1511 100644 --- a/stackdriver/testdata/80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab.toml +++ b/debugger/testdata/80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab.toml @@ -1,2 +1,2 @@ -uri = "https://localhost/stub-stackdriver-debugger-agent.tar.gz" +uri = "https://localhost/stub-cloud-debugger-agent.tar.gz" sha256 = "80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab" diff --git a/stackdriver/testdata/80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab/stub-stackdriver-debugger-agent.tar.gz b/debugger/testdata/80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab/stub-cloud-debugger-agent.tar.gz similarity index 100% rename from stackdriver/testdata/80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab/stub-stackdriver-debugger-agent.tar.gz rename to debugger/testdata/80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab/stub-cloud-debugger-agent.tar.gz diff --git a/stackdriver/testdata/c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6.toml b/debugger/testdata/c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6.toml similarity index 55% rename from stackdriver/testdata/c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6.toml rename to debugger/testdata/c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6.toml index 765098e..d9bb6a9 100644 --- a/stackdriver/testdata/c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6.toml +++ b/debugger/testdata/c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6.toml @@ -1,2 +1,2 @@ -uri = "https://localhost/stub-stackdriver-debugger-agent.tgz" +uri = "https://localhost/stub-cloud-debugger-agent.tgz" sha256 = "c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6" diff --git a/stackdriver/testdata/c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6/stub-stackdriver-debugger-agent.tgz b/debugger/testdata/c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6/stub-cloud-debugger-agent.tgz similarity index 100% rename from stackdriver/testdata/c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6/stub-stackdriver-debugger-agent.tgz rename to debugger/testdata/c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6/stub-cloud-debugger-agent.tgz diff --git a/detect.go b/detect.go new file mode 100644 index 0000000..10fd42d --- /dev/null +++ b/detect.go @@ -0,0 +1,213 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package google + +import ( + "fmt" + + "github.com/buildpacks/libcnb" + + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/libpak" +) + +const ( + DebuggerEnabled = "BP_GOOGLE_CLOUD_DEBUGGER_ENABLED" + ProfilerEnabled = "BP_GOOGLE_CLOUD_PROFILER_ENABLED" +) + +type Detect struct{} + +func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) { + cr, err := libpak.NewConfigurationResolver(context.Buildpack, nil) + if err != nil { + return libcnb.DetectResult{}, fmt.Errorf("unable to create configuration resolver\n%w", err) + } + + debugger := cr.ResolveBool(DebuggerEnabled) + profiler := cr.ResolveBool(ProfilerEnabled) + + if debugger && profiler { + return libcnb.DetectResult{ + Pass: true, + Plans: []libcnb.BuildPlan{ + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.ProfilerJava}, + {Name: common.DebuggerNodeJS}, + {Name: common.ProfilerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.ProfilerJava}, + {Name: "jvm-application"}, + {Name: common.DebuggerNodeJS}, + {Name: common.ProfilerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.ProfilerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: "jvm-application"}, + {Name: common.ProfilerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + {Name: common.DebuggerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + {Name: "jvm-application"}, + {Name: common.DebuggerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.ProfilerJava}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.ProfilerJava}, + {Name: "jvm-application"}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerNodeJS}, + {Name: common.ProfilerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerNodeJS}, + {Name: common.ProfilerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + }, + }, nil + } + + if debugger { + return libcnb.DetectResult{ + Pass: true, + Plans: []libcnb.BuildPlan{ + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.DebuggerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: "jvm-application"}, + {Name: common.DebuggerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: "jvm-application"}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + }, + }, nil + } + + if profiler { + return libcnb.DetectResult{ + Pass: true, + Plans: []libcnb.BuildPlan{ + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + {Name: common.ProfilerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + {Name: "jvm-application"}, + {Name: common.ProfilerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + {Name: "jvm-application"}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.ProfilerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.ProfilerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + }, + }, nil + } + + return libcnb.DetectResult{Pass: false}, nil +} diff --git a/detect_test.go b/detect_test.go new file mode 100644 index 0000000..83b66e1 --- /dev/null +++ b/detect_test.go @@ -0,0 +1,244 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package google_test + +import ( + "os" + "testing" + + "github.com/buildpacks/libcnb" + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + + "github.com/paketo-buildpacks/google-cloud" + "github.com/paketo-buildpacks/google-cloud/internal/common" +) + +func testDetect(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + + ctx libcnb.DetectContext + detect google.Detect + ) + + it("fails without any environment variables", func() { + Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{})) + }) + + context("$BP_GOOGLE_CLOUD_DEBUGGER_ENABLED and $BP_GOOGLE_CLOUD_PROFILER_ENABLED", func() { + it.Before(func() { + Expect(os.Setenv(google.DebuggerEnabled, "true")).To(Succeed()) + Expect(os.Setenv(google.ProfilerEnabled, "true")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv(google.DebuggerEnabled)).To(Succeed()) + Expect(os.Unsetenv(google.ProfilerEnabled)).To(Succeed()) + }) + + it("passes with debugger and profiler enabled", func() { + Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{ + Pass: true, + Plans: []libcnb.BuildPlan{ + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.ProfilerJava}, + {Name: common.DebuggerNodeJS}, + {Name: common.ProfilerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.ProfilerJava}, + {Name: "jvm-application"}, + {Name: common.DebuggerNodeJS}, + {Name: common.ProfilerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.ProfilerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: "jvm-application"}, + {Name: common.ProfilerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + {Name: common.DebuggerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + {Name: "jvm-application"}, + {Name: common.DebuggerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.ProfilerJava}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.ProfilerJava}, + {Name: "jvm-application"}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerNodeJS}, + {Name: common.ProfilerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerNodeJS}, + {Name: common.ProfilerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + }, + })) + }) + }) + + context("$BP_GOOGLE_CLOUD_DEBUGGER_ENABLED", func() { + it.Before(func() { + Expect(os.Setenv(google.DebuggerEnabled, "true")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv(google.DebuggerEnabled)).To(Succeed()) + }) + + it("passes with debugger enabled", func() { + Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{ + Pass: true, + Plans: []libcnb.BuildPlan{ + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: common.DebuggerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: "jvm-application"}, + {Name: common.DebuggerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerJava}, + {Name: "jvm-application"}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.DebuggerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.DebuggerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + }, + })) + }) + + }) + + context("$BP_GOOGLE_CLOUD_PROFILER_ENABLED", func() { + it.Before(func() { + Expect(os.Setenv(google.ProfilerEnabled, "true")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv(google.ProfilerEnabled)).To(Succeed()) + }) + + it("passes with profiler enabled", func() { + Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{ + Pass: true, + Plans: []libcnb.BuildPlan{ + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + {Name: common.ProfilerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + {Name: "jvm-application"}, + {Name: common.ProfilerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.ProfilerJava}, + {Name: "jvm-application"}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: common.Credentials}, + {Name: common.ProfilerNodeJS}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: common.Credentials}, + {Name: common.ProfilerNodeJS}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + }, + })) + }) + }) + +} diff --git a/go.mod b/go.mod index e995654..a0112b4 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,15 @@ -module github.com/paketo-buildpacks/google-stackdriver +module github.com/paketo-buildpacks/google-cloud go 1.15 require ( github.com/buildpacks/libcnb v1.18.1 + github.com/mattn/go-colorable v0.1.8 // indirect github.com/onsi/gomega v1.10.3 github.com/paketo-buildpacks/libpak v1.50.0 github.com/sclevine/spec v1.4.0 github.com/stretchr/testify v1.6.1 + golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637 // indirect ) + +replace github.com/paketo-buildpacks/libpak => ../../paketo-buildpacks/libpak diff --git a/go.sum b/go.sum index 539f602..f9c9f99 100644 --- a/go.sum +++ b/go.sum @@ -6,7 +6,6 @@ github.com/buildpacks/libcnb v1.18.1 h1:NcAqtyLmSkpcgIOl83GOHY5Mk2ltBFiAI8mmAvav github.com/buildpacks/libcnb v1.18.1/go.mod h1:ozKZYold67tlck+1cobg11YhGmJqkQr8bMAH5gSvEvw= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -31,8 +30,12 @@ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= @@ -43,8 +46,6 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/paketo-buildpacks/libpak v1.50.0 h1:o+KmJFWgLI/ygd4Nru8A1EuE3xJ1a0BuhsWUIVB8BPI= -github.com/paketo-buildpacks/libpak v1.50.0/go.mod h1:0JfeM5G7dtsan6yDjL8X60L+Jdiz6REVnxgkBVRp+ho= github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -71,9 +72,12 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637 h1:O5hKNaGxIT4A8OTMnuh6UpmBdI3SAPxlZ3g0olDrJVM= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/helper/credentials.go b/helper/credentials.go deleted file mode 100644 index add0cc6..0000000 --- a/helper/credentials.go +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package helper - -import ( - "fmt" - - "github.com/buildpacks/libcnb" - "github.com/paketo-buildpacks/libpak/bard" - "github.com/paketo-buildpacks/libpak/bindings" -) - -type Credentials struct { - Bindings libcnb.Bindings - Logger bard.Logger -} - -func (c Credentials) Execute() (map[string]string, error) { - if b, ok, err := bindings.ResolveOne(c.Bindings, bindings.OfType("StackdriverDebugger")); err != nil { - return nil, fmt.Errorf("unable to resolve binding StackdriverDebugger\n%w", err) - } else if ok { - if p, ok := b.SecretFilePath("ApplicationCredentials"); ok { - c.Logger.Info("Configuring Google application credentials") - return map[string]string{"GOOGLE_APPLICATION_CREDENTIALS": p}, nil - } - } - - if b, ok, err := bindings.ResolveOne(c.Bindings, bindings.OfType("StackdriverProfiler")); err != nil { - return nil, fmt.Errorf("unable to resolve binding StackdriverProfiler\n%w", err) - } else if ok { - if p, ok := b.SecretFilePath("ApplicationCredentials"); ok { - c.Logger.Info("Configuring Google application credentials") - return map[string]string{"GOOGLE_APPLICATION_CREDENTIALS": p}, nil - } - } - - return nil, nil -} diff --git a/helper/credentials_test.go b/helper/credentials_test.go deleted file mode 100644 index c53b1ad..0000000 --- a/helper/credentials_test.go +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package helper_test - -import ( - "testing" - - "github.com/buildpacks/libcnb" - . "github.com/onsi/gomega" - "github.com/sclevine/spec" - - "github.com/paketo-buildpacks/google-stackdriver/helper" -) - -func testCredentials(t *testing.T, context spec.G, it spec.S) { - var ( - Expect = NewWithT(t).Expect - - c helper.Credentials - ) - - it("does not contribute properties if no binding exists", func() { - Expect(c.Execute()).To(BeZero()) - }) - - it("contributes credentials if debugger binding exists", func() { - c.Bindings = libcnb.Bindings{ - { - Name: "test-binding", - Path: "/test/path/test-binding", - Type: "StackdriverDebugger", - Secret: map[string]string{"ApplicationCredentials": "test-value"}, - }, - } - - Expect(c.Execute()).To(Equal(map[string]string{ - "GOOGLE_APPLICATION_CREDENTIALS": "/test/path/test-binding/ApplicationCredentials", - })) - }) - - it("contributes credentials if profiler binding exists", func() { - c.Bindings = libcnb.Bindings{ - { - Name: "test-binding", - Path: "/test/path/test-binding", - Type: "StackdriverProfiler", - Secret: map[string]string{"ApplicationCredentials": "test-value"}, - }, - } - - Expect(c.Execute()).To(Equal(map[string]string{ - "GOOGLE_APPLICATION_CREDENTIALS": "/test/path/test-binding/ApplicationCredentials", - })) - }) -} diff --git a/helper/java_debugger.go b/helper/java_debugger.go deleted file mode 100644 index acbfbe2..0000000 --- a/helper/java_debugger.go +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package helper - -import ( - "fmt" - "os" - "strings" - - "github.com/paketo-buildpacks/libpak/bard" -) - -type JavaDebugger struct { - Logger bard.Logger -} - -func (j JavaDebugger) Execute() (map[string]string, error) { - module, ok := os.LookupEnv("BPL_GOOGLE_STACKDRIVER_MODULE") - if !ok { - module = "default-module" - } - - version := os.Getenv("BPL_GOOGLE_STACKDRIVER_VERSION") - - var values []string - if s, ok := os.LookupEnv("JAVA_TOOL_OPTIONS"); ok { - values = append(values, s) - } - - values = append(values, - "-Dcom.google.cdbg.auth.serviceaccount.enable=true", - fmt.Sprintf("-Dcom.google.cdbg.module=%s", module), - ) - - if version != "" { - values = append(values, fmt.Sprintf("-Dcom.google.cdbg.version=%s", version)) - } - - message := fmt.Sprintf("Google Stackdriver Debugger enabled for %s", module) - if version != "" { - message = fmt.Sprintf("%s:%s", message, version) - } - j.Logger.Info(message) - - return map[string]string{"JAVA_TOOL_OPTIONS": strings.Join(values, " ")}, nil -} diff --git a/helper/java_debugger_test.go b/helper/java_debugger_test.go deleted file mode 100644 index 45e4bcf..0000000 --- a/helper/java_debugger_test.go +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package helper_test - -import ( - "os" - "testing" - - . "github.com/onsi/gomega" - "github.com/sclevine/spec" - - "github.com/paketo-buildpacks/google-stackdriver/helper" -) - -func testJavaDebugger(t *testing.T, context spec.G, it spec.S) { - var ( - Expect = NewWithT(t).Expect - - j helper.JavaDebugger - ) - - it("uses defaults", func() { - Expect(j.Execute()).To(Equal(map[string]string{ - "JAVA_TOOL_OPTIONS": "-Dcom.google.cdbg.auth.serviceaccount.enable=true -Dcom.google.cdbg.module=default-module", - })) - }) - - context("$BPL_GOOGLE_STACKDRIVER_MODULE", func() { - it.Before(func() { - Expect(os.Setenv("BPL_GOOGLE_STACKDRIVER_MODULE", "test-module")).To(Succeed()) - }) - - it.After(func() { - Expect(os.Unsetenv("BPL_GOOGLE_STACKDRIVER_MODULE")).To(Succeed()) - }) - - it("uses configured module", func() { - Expect(j.Execute()).To(Equal(map[string]string{ - "JAVA_TOOL_OPTIONS": "-Dcom.google.cdbg.auth.serviceaccount.enable=true -Dcom.google.cdbg.module=test-module", - })) - }) - }) - - context("$BPL_GOOGLE_STACKDRIVER_VERSION", func() { - it.Before(func() { - Expect(os.Setenv("BPL_GOOGLE_STACKDRIVER_VERSION", "test-version")).To(Succeed()) - }) - - it.After(func() { - Expect(os.Unsetenv("BPL_GOOGLE_STACKDRIVER_VERSION")).To(Succeed()) - }) - - it("uses configured version", func() { - Expect(j.Execute()).To(Equal(map[string]string{ - "JAVA_TOOL_OPTIONS": "-Dcom.google.cdbg.auth.serviceaccount.enable=true -Dcom.google.cdbg.module=default-module -Dcom.google.cdbg.version=test-version", - })) - }) - }) - - context("$JAVA_TOOL_OPTIONS", func() { - it.Before(func() { - Expect(os.Setenv("JAVA_TOOL_OPTIONS", "test-java-tool-options")).To(Succeed()) - }) - - it.After(func() { - Expect(os.Unsetenv("JAVA_TOOL_OPTIONS")).To(Succeed()) - }) - - it("uses configured JAVA_TOOL_OPTIONS", func() { - Expect(j.Execute()).To(Equal(map[string]string{ - "JAVA_TOOL_OPTIONS": "test-java-tool-options -Dcom.google.cdbg.auth.serviceaccount.enable=true -Dcom.google.cdbg.module=default-module", - })) - }) - }) -} diff --git a/helper/java_profiler.go b/helper/java_profiler.go deleted file mode 100644 index 8ddfffa..0000000 --- a/helper/java_profiler.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package helper - -import ( - "fmt" - "os" - "strings" - - "github.com/paketo-buildpacks/libpak/bard" -) - -type JavaProfiler struct { - Logger bard.Logger -} - -func (j JavaProfiler) Execute() (map[string]string, error) { - module, ok := os.LookupEnv("BPL_GOOGLE_STACKDRIVER_MODULE") - if !ok { - module = "default-module" - } - - projectId := os.Getenv("BPL_GOOGLE_STACKDRIVER_PROJECT_ID") - - version := os.Getenv("BPL_GOOGLE_STACKDRIVER_VERSION") - - agentPath, ok := os.LookupEnv("BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH") - if !ok { - return nil, fmt.Errorf("$BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH must be set") - } - - var values []string - if s, ok := os.LookupEnv("JAVA_TOOL_OPTIONS"); ok { - values = append(values, s) - } - - agent := fmt.Sprintf("-agentpath:%s=-logtostderr=1,-cprof_project_id=%s,-cprof_service=%s", - agentPath, projectId, module) - if version != "" { - agent = fmt.Sprintf("%s,-cprof_service_version=%s", agent, version) - } - values = append(values, agent) - - message := fmt.Sprintf("Google Stackdriver Profiler enabled for %s", module) - if version != "" { - message = fmt.Sprintf("%s:%s", message, version) - } - j.Logger.Info(message) - - return map[string]string{"JAVA_TOOL_OPTIONS": strings.Join(values, " ")}, nil -} diff --git a/helper/java_profiler_test.go b/helper/java_profiler_test.go deleted file mode 100644 index b9e5db5..0000000 --- a/helper/java_profiler_test.go +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package helper_test - -import ( - "os" - "testing" - - . "github.com/onsi/gomega" - "github.com/sclevine/spec" - - "github.com/paketo-buildpacks/google-stackdriver/helper" -) - -func testJavaProfiler(t *testing.T, context spec.G, it spec.S) { - var ( - Expect = NewWithT(t).Expect - - j helper.JavaProfiler - ) - - it("fails without $BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH", func() { - _, err := j.Execute() - Expect(err).To(MatchError("$BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH must be set")) - }) - - context("$BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH", func() { - - it.Before(func() { - Expect(os.Setenv("BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH", "test-path")).To(Succeed()) - }) - - it.After(func() { - Expect(os.Unsetenv("BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH")).To(Succeed()) - }) - - it("uses defaults", func() { - Expect(j.Execute()).To(Equal(map[string]string{ - "JAVA_TOOL_OPTIONS": "-agentpath:test-path=-logtostderr=1,-cprof_project_id=,-cprof_service=default-module", - })) - }) - - context("$BPL_GOOGLE_STACKDRIVER_MODULE", func() { - it.Before(func() { - Expect(os.Setenv("BPL_GOOGLE_STACKDRIVER_MODULE", "test-module")).To(Succeed()) - }) - - it.After(func() { - Expect(os.Unsetenv("BPL_GOOGLE_STACKDRIVER_MODULE")).To(Succeed()) - }) - - it("uses configured module", func() { - Expect(j.Execute()).To(Equal(map[string]string{ - "JAVA_TOOL_OPTIONS": "-agentpath:test-path=-logtostderr=1,-cprof_project_id=,-cprof_service=test-module", - })) - }) - }) - - context("$BPL_GOOGLE_STACKDRIVER_VERSION", func() { - it.Before(func() { - Expect(os.Setenv("BPL_GOOGLE_STACKDRIVER_VERSION", "test-version")).To(Succeed()) - }) - - it.After(func() { - Expect(os.Unsetenv("BPL_GOOGLE_STACKDRIVER_VERSION")).To(Succeed()) - }) - - it("uses configured version", func() { - Expect(j.Execute()).To(Equal(map[string]string{ - "JAVA_TOOL_OPTIONS": "-agentpath:test-path=-logtostderr=1,-cprof_project_id=,-cprof_service=default-module,-cprof_service_version=test-version", - })) - }) - }) - - context("$JAVA_TOOL_OPTIONS", func() { - it.Before(func() { - Expect(os.Setenv("JAVA_TOOL_OPTIONS", "test-java-tool-options")).To(Succeed()) - }) - - it.After(func() { - Expect(os.Unsetenv("JAVA_TOOL_OPTIONS")).To(Succeed()) - }) - - it("uses configured JAVA_TOOL_OPTIONS", func() { - Expect(j.Execute()).To(Equal(map[string]string{ - "JAVA_TOOL_OPTIONS": "test-java-tool-options -agentpath:test-path=-logtostderr=1,-cprof_project_id=,-cprof_service=default-module", - })) - }) - }) - }) -} diff --git a/stackdriver/init_test.go b/init_test.go similarity index 72% rename from stackdriver/init_test.go rename to init_test.go index 777525d..7e27fb3 100644 --- a/stackdriver/init_test.go +++ b/init_test.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package stackdriver_test +package google_test import ( "testing" @@ -24,12 +24,8 @@ import ( ) func TestUnit(t *testing.T) { - suite := spec.New("stackdriver", spec.Report(report.Terminal{})) + suite := spec.New("google", spec.Report(report.Terminal{})) suite("Build", testBuild) suite("Detect", testDetect) - suite("JavaDebuggerAgent", testJavaDebuggerAgent) - suite("JavaProfilerAgent", testJavaProfilerAgent) - suite("NodeJSDebuggerAgent", testNodeJSDebuggerAgent) - suite("NodeJSProfileAgent", testNodeJSProfilerAgent) suite.Run(t) } diff --git a/internal/common/common.go b/internal/common/common.go new file mode 100644 index 0000000..2ad6aa2 --- /dev/null +++ b/internal/common/common.go @@ -0,0 +1,39 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package common + +const ( + Module = "BPL_GOOGLE_CLOUD_MODULE" + ProjectID = "BPL_GOOGLE_CLOUD_PROJECT_ID" + Version = "BPL_GOOGLE_CLOUD_VERSION" +) + +const ( + Credentials = "google-cloud-credentials" + DebuggerJava = "google-cloud-debugger-java" + DebuggerNodeJS = "google-cloud-debugger-nodejs" + ProfilerJava = "google-cloud-profiler-java" + ProfilerNodeJS = "google-cloud-profiler-nodejs" +) + +type CredentialSource uint8 + +const ( + Binding CredentialSource = iota + MetadataServer + None +) diff --git a/internal/nodejs/init_test.go b/internal/nodejs/init_test.go new file mode 100644 index 0000000..557a067 --- /dev/null +++ b/internal/nodejs/init_test.go @@ -0,0 +1,30 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package nodejs_test + +import ( + "testing" + + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestUnit(t *testing.T) { + suite := spec.New("nodejs", spec.Report(report.Terminal{})) + suite("Module", testModule) + suite.Run(t) +} diff --git a/internal/nodejs/module.go b/internal/nodejs/module.go new file mode 100644 index 0000000..152bf65 --- /dev/null +++ b/internal/nodejs/module.go @@ -0,0 +1,85 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package nodejs + +import ( + "bytes" + "fmt" + "regexp" + "text/template" +) + +func IsModuleRequired(module string, content []byte) (bool, error) { + p := fmt.Sprintf(`require\(['"]%s['"]\)`, module) + + r, err := regexp.Compile(p) + if err != nil { + return false, fmt.Errorf("unable to compiler regex '%s'\n%w", p, err) + } + + return r.Match(content), nil +} + +func RequireModule(context ModuleContext) ([]byte, error) { + t, err := template.New("require-module"). + Parse(`require('{{.Module}}').start({ + serviceContext: { + service: '{{.Service}}', + version: '{{.Version}}', + }, +}); +`) + if err != nil { + return nil, fmt.Errorf("unable to parse template\n%w", err) + } + + b := &bytes.Buffer{} + if err := t.Execute(b, context); err != nil { + return nil, fmt.Errorf("unable to execute template\n%w", err) + } + + return b.Bytes(), nil +} + +func RequireModuleExternal(context ModuleContext) ([]byte, error) { + t, err := template.New("require-module-external"). + Parse(`require('{{.Module}}').start({ + projectId: '{{.ProjectId}}', + serviceContext: { + service: '{{.Service}}', + version: '{{.Version}}', + }, +}); +`) + if err != nil { + return nil, fmt.Errorf("unable to parse template\n%w", err) + } + + b := &bytes.Buffer{} + if err := t.Execute(b, context); err != nil { + return nil, fmt.Errorf("unable to execute template\n%w", err) + } + + return b.Bytes(), nil +} + +type ModuleContext struct { + Module string + ProjectId string + Service string + Version string +} diff --git a/internal/nodejs/module_test.go b/internal/nodejs/module_test.go new file mode 100644 index 0000000..adb2006 --- /dev/null +++ b/internal/nodejs/module_test.go @@ -0,0 +1,93 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package nodejs_test + +import ( + "testing" + + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + + "github.com/paketo-buildpacks/google-cloud/internal/nodejs" +) + +func testModule(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + ) + + context("IsModuleRequired", func() { + it("detects required module", func() { + Expect(nodejs.IsModuleRequired("test-module", []byte(`require('test-module')`))).To(BeTrue()) + Expect(nodejs.IsModuleRequired("test-module", []byte(`require("test-module")`))).To(BeTrue()) + Expect(nodejs.IsModuleRequired("test-module", []byte(`test-data +require('test-module') +test-data`))).To(BeTrue()) + }) + + it("detects not-required module", func() { + Expect(nodejs.IsModuleRequired("test-module", []byte(`require('another-module')`))).To(BeFalse()) + Expect(nodejs.IsModuleRequired("test-module", []byte(`require("another-module")`))).To(BeFalse()) + Expect(nodejs.IsModuleRequired("test-module", []byte(`test-data +require('another-module') +test-data`))).To(BeFalse()) + }) + }) + + context("RequireModule", func() { + + it("renders require module", func() { + b, err := nodejs.RequireModule(nodejs.ModuleContext{ + Module: "test-module", + Service: "test-service", + Version: "test-version", + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(b)).To(Equal(`require('test-module').start({ + serviceContext: { + service: 'test-service', + version: 'test-version', + }, +}); +`)) + }) + }) + + context("RequireModuleExternal", func() { + + it("renders require module", func() { + b, err := nodejs.RequireModuleExternal(nodejs.ModuleContext{ + Module: "test-module", + ProjectId: "test-project-id", + Service: "test-service", + Version: "test-version", + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(b)).To(Equal(`require('test-module').start({ + projectId: 'test-project-id', + serviceContext: { + service: 'test-service', + version: 'test-version', + }, +}); +`)) + }) + + }) +} diff --git a/profiler/init_test.go b/profiler/init_test.go new file mode 100644 index 0000000..14bc00f --- /dev/null +++ b/profiler/init_test.go @@ -0,0 +1,31 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package profiler_test + +import ( + "testing" + + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestUnit(t *testing.T) { + suite := spec.New("profiler", spec.Report(report.Terminal{})) + suite("Java", testJava) + suite("NodeJS", testNodeJS) + suite.Run(t) +} diff --git a/profiler/java.go b/profiler/java.go new file mode 100644 index 0000000..db81cd8 --- /dev/null +++ b/profiler/java.go @@ -0,0 +1,115 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package profiler + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/buildpacks/libcnb" + + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/libpak" + "github.com/paketo-buildpacks/libpak/bard" + "github.com/paketo-buildpacks/libpak/crush" + "github.com/paketo-buildpacks/libpak/sherpa" +) + +const AgentPath = "BPI_GOOGLE_CLOUD_PROFILER_AGENT_PATH" + +type JavaBuild struct { + LayerContributor libpak.DependencyLayerContributor + Logger bard.Logger +} + +func NewJavaBuild(dependency libpak.BuildpackDependency, cache libpak.DependencyCache, plan *libcnb.BuildpackPlan) JavaBuild { + return JavaBuild{LayerContributor: libpak.NewDependencyLayerContributor(dependency, cache, plan)} +} + +func (j JavaBuild) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { + j.LayerContributor.Logger = j.Logger + + return j.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { + j.Logger.Bodyf("Expanding to %s", layer.Path) + + if err := crush.ExtractTarGz(artifact, layer.Path, 0); err != nil { + return libcnb.Layer{}, fmt.Errorf("unable to extract %s\n%w", artifact.Name(), err) + } + + layer.LaunchEnvironment.Default(AgentPath, filepath.Join(layer.Path, "profiler_java_agent.so")) + + return layer, nil + }, libpak.LaunchLayer) + +} + +func (j JavaBuild) Name() string { + return j.LayerContributor.LayerName() +} + +type JavaLaunch struct { + CredentialSource common.CredentialSource + Logger bard.Logger +} + +// https://cloud.google.com/profiler/docs/profiling-java#agent_configuration +// https://cloud.google.com/profiler/docs/profiling-external#linking_the_agent_to_a_project +func (j JavaLaunch) Execute() (map[string]string, error) { + if j.CredentialSource == common.None { + j.Logger.Info("Google Cloud Profiler disabled") + return nil, nil + } + + p, err := sherpa.GetEnvRequired(AgentPath) + if err != nil { + return nil, err + } + + m, err := sherpa.GetEnvRequired(common.Module) + if err != nil { + return nil, err + } + + v, err := sherpa.GetEnvRequired(common.Version) + if err != nil { + return nil, err + } + + j.Logger.Infof("Google Cloud Profiler enabled (%s:%s)", m, v) + + var agent []string + agent = append(agent, + fmt.Sprintf("-agentpath:%s=-logtostderr=1", p), + fmt.Sprintf("-cprof_service=%s", m), + fmt.Sprintf("-cprof_service_version=%s", v), + ) + + if j.CredentialSource == common.Binding { + projectId, err := sherpa.GetEnvRequired(common.ProjectID) + if err != nil { + return nil, err + } + + agent = append(agent, fmt.Sprintf("-cprof_project_id=%s", projectId)) + } + + return map[string]string{ + "JAVA_TOOL_OPTIONS": sherpa.AppendToEnvVar("JAVA_TOOL_OPTIONS", " ", strings.Join(agent, ",")), + }, nil +} diff --git a/profiler/java_test.go b/profiler/java_test.go new file mode 100644 index 0000000..74a0ecd --- /dev/null +++ b/profiler/java_test.go @@ -0,0 +1,193 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package profiler_test + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/buildpacks/libcnb" + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/google-cloud/profiler" + "github.com/paketo-buildpacks/libpak" +) + +func testJava(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + ) + + context("Build", func() { + var ( + ctx libcnb.BuildContext + ) + + it.Before(func() { + var err error + ctx.Layers.Path, err = ioutil.TempDir("", "profiler-java-build-layers") + Expect(err).NotTo(HaveOccurred()) + }) + + it.After(func() { + Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) + }) + + it("contributes Java agent", func() { + dep := libpak.BuildpackDependency{ + URI: "https://localhost/stub-cloud-profiler-agent.tar.gz", + SHA256: "a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797", + } + dc := libpak.DependencyCache{CachePath: "testdata"} + + layer, err := ctx.Layers.Layer("test-layer") + Expect(err).NotTo(HaveOccurred()) + + layer, err = profiler.NewJavaBuild(dep, dc, &libcnb.BuildpackPlan{}).Contribute(layer) + Expect(err).NotTo(HaveOccurred()) + + Expect(layer.Launch).To(BeTrue()) + + file := filepath.Join(layer.Path, "profiler_java_agent.so") + Expect(file).To(BeARegularFile()) + Expect(layer.LaunchEnvironment[fmt.Sprintf("%s.default", profiler.AgentPath)]).To(Equal(file)) + }) + + }) + + + context("Launch", func() { + var ( + l = profiler.JavaLaunch{ + CredentialSource: common.MetadataServer, + } + ) + + it.Before(func() { + Expect(os.Setenv(profiler.AgentPath, "test-path")).To(Succeed()) + Expect(os.Setenv(common.Module, "test-module")).To(Succeed()) + Expect(os.Setenv(common.Version, "test-version")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv(profiler.AgentPath)).To(Succeed()) + Expect(os.Unsetenv(common.Module)).To(Succeed()) + Expect(os.Unsetenv(common.Version)).To(Succeed()) + }) + + it("does not contribute if source is None", func() { + l.CredentialSource = common.None + + Expect(l.Execute()).To(BeNil()) + }) + + it("returns error if BPI_GOOGLE_CLOUD_PROFILER_AGENT_PATH is not set", func() { + Expect(os.Unsetenv(profiler.AgentPath)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPI_GOOGLE_CLOUD_PROFILER_AGENT_PATH must be set")) + }) + + it("returns error if BPL_GOOGLE_CLOUD_MODULE is not set", func() { + Expect(os.Unsetenv(common.Module)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPL_GOOGLE_CLOUD_MODULE must be set")) + }) + + it("returns error if BPL_GOOGLE_CLOUD_VERSION is not set", func() { + Expect(os.Unsetenv(common.Version)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPL_GOOGLE_CLOUD_VERSION must be set")) + }) + + context("binding", func() { + + it.Before(func() { + l.CredentialSource = common.Binding + Expect(os.Setenv(common.ProjectID, "test-project-id")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv(common.ProjectID)).To(Succeed()) + }) + + it("returns error if BPL_GOOGLE_CLOUD_PROJECT_ID is not set", func() { + Expect(os.Unsetenv(common.ProjectID)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPL_GOOGLE_CLOUD_PROJECT_ID must be set")) + }) + + it("contributes JAVA_TOOL_OPTIONS", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "JAVA_TOOL_OPTIONS": strings.Join([]string{ + "-agentpath:test-path=-logtostderr=1", + "-cprof_service=test-module", + "-cprof_service_version=test-version", + "-cprof_project_id=test-project-id", + }, ","), + })) + + }) + }) + + context("metadata server", func() { + + it("contributes JAVA_TOOL_OPTIONS", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "JAVA_TOOL_OPTIONS": strings.Join([]string{ + "-agentpath:test-path=-logtostderr=1", + "-cprof_service=test-module", + "-cprof_service_version=test-version", + }, ","), + })) + }) + }) + + context("existing $JAVA_TOOL_OPTIONS", func() { + + it.Before(func() { + Expect(os.Setenv("JAVA_TOOL_OPTIONS", "test-java-tool-options")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("JAVA_TOOL_OPTIONS")).To(Succeed()) + }) + + it("contributes JAVA_TOOL_OPTIONS", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "JAVA_TOOL_OPTIONS": strings.Join([]string{ + "test-java-tool-options", + strings.Join([]string{ + "-agentpath:test-path=-logtostderr=1", + "-cprof_service=test-module", + "-cprof_service_version=test-version", + }, ","), + }, " "), + })) + }) + }) + }) +} diff --git a/profiler/nodejs.go b/profiler/nodejs.go new file mode 100644 index 0000000..17041ce --- /dev/null +++ b/profiler/nodejs.go @@ -0,0 +1,158 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package profiler + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/buildpacks/libcnb" + + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/google-cloud/internal/nodejs" + "github.com/paketo-buildpacks/libpak" + "github.com/paketo-buildpacks/libpak/bard" + "github.com/paketo-buildpacks/libpak/effect" + "github.com/paketo-buildpacks/libpak/sherpa" +) + +const ( + NodeModule = "@google-cloud/profiler" + NodePath = "BPI_GOOGLE_CLOUD_PROFILER_NODE_PATH" +) + +type NodeJSBuild struct { + Executor effect.Executor + LayerContributor libpak.DependencyLayerContributor + Logger bard.Logger +} + +func NewNodeJSBuild(dependency libpak.BuildpackDependency, cache libpak.DependencyCache, plan *libcnb.BuildpackPlan) NodeJSBuild { + return NodeJSBuild{ + Executor: effect.NewExecutor(), + LayerContributor: libpak.NewDependencyLayerContributor(dependency, cache, plan), + } +} + +func (n NodeJSBuild) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { + n.LayerContributor.Logger = n.Logger + + return n.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { + n.Logger.Bodyf("Installing to %s", layer.Path) + + if err := n.Executor.Execute(effect.Execution{ + Command: "npm", + Args: []string{"install", "--no-save", artifact.Name()}, + Dir: layer.Path, + Stdout: n.Logger.InfoWriter(), + Stderr: n.Logger.InfoWriter(), + }); err != nil { + return libcnb.Layer{}, fmt.Errorf("unable to run npm install\n%w", err) + } + + layer.LaunchEnvironment.Default(NodePath, filepath.Join(layer.Path, "node_modules")) + + return layer, nil + }, libpak.LaunchLayer) +} + +func (n NodeJSBuild) Name() string { + return n.LayerContributor.LayerName() +} + +type NodeJSLaunch struct { + ApplicationPath string + CredentialSource common.CredentialSource + Logger bard.Logger +} + +// https://cloud.google.com/profiler/docs/profiling-nodejs#installation +// https://cloud.google.com/profiler/docs/profiling-external#node.js +func (n NodeJSLaunch) Execute() (map[string]string, error) { + if n.CredentialSource == common.None { + n.Logger.Info("Google Cloud Profiler disabled") + return nil, nil + } + + p, err := sherpa.GetEnvRequired(NodePath) + if err != nil { + return nil, err + } + + s, err := sherpa.GetEnvRequired(common.Module) + if err != nil { + return nil, err + } + + v, err := sherpa.GetEnvRequired(common.Version) + if err != nil { + return nil, err + } + + n.Logger.Infof("Google Cloud Profiler enabled (%s:%s)", s, v) + + mod, err := sherpa.NodeJSMainModule(n.ApplicationPath) + if err != nil { + return nil, fmt.Errorf("unable to find main module in %s\n%w", n.ApplicationPath, err) + } + + file := filepath.Join(n.ApplicationPath, mod) + c, err := ioutil.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("unable to read contents of %s\n%w", file, err) + } + + e, err := nodejs.IsModuleRequired(NodeModule, c) + if err != nil { + return nil, fmt.Errorf("unable to determine if %s is already required\n%w", NodeModule, err) + } + + if !e { + n.Logger.Headerf("Requiring '%s' module", NodeModule) + + mc := nodejs.ModuleContext{ + Module: NodeModule, + Service: s, + Version: v, + } + + var b []byte + if n.CredentialSource == common.Binding { + mc.ProjectId, err = sherpa.GetEnvRequired(common.ProjectID) + if err != nil { + return nil, err + } + + b, err = nodejs.RequireModuleExternal(mc) + } else { + b, err = nodejs.RequireModule(mc) + } + if err != nil { + return nil, fmt.Errorf("unable to create require prologue\n%w", err) + } + + if err := ioutil.WriteFile(file, append(b, c...), 0644); err != nil { + return nil, fmt.Errorf("unable to write main module %s\n%w", file, err) + } + } + + return map[string]string{ + "NODE_PATH": sherpa.AppendToEnvVar("NODE_PATH", string(os.PathListSeparator), p), + }, nil +} diff --git a/profiler/nodejs_test.go b/profiler/nodejs_test.go new file mode 100644 index 0000000..e41ff78 --- /dev/null +++ b/profiler/nodejs_test.go @@ -0,0 +1,287 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package profiler_test + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/buildpacks/libcnb" + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + "github.com/stretchr/testify/mock" + + "github.com/paketo-buildpacks/google-cloud/internal/common" + "github.com/paketo-buildpacks/google-cloud/profiler" + "github.com/paketo-buildpacks/libpak" + "github.com/paketo-buildpacks/libpak/effect" + "github.com/paketo-buildpacks/libpak/effect/mocks" +) + +func testNodeJS(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + ) + + context("Build", func() { + var ( + ctx libcnb.BuildContext + executor *mocks.Executor + ) + + it.Before(func() { + var err error + ctx.Layers.Path, err = ioutil.TempDir("", "profiler-nodejs-build-layers") + Expect(err).NotTo(HaveOccurred()) + + executor = &mocks.Executor{} + executor.On("Execute", mock.Anything).Return(nil) + }) + + it.After(func() { + Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) + }) + + it("contributes NodeJS agent", func() { + dep := libpak.BuildpackDependency{ + URI: "https://localhost/stub-cloud-profiler-agent.tgz", + SHA256: "eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138", + } + dc := libpak.DependencyCache{CachePath: "testdata"} + + n := profiler.NewNodeJSBuild(dep, dc, &libcnb.BuildpackPlan{}) + n.Executor = executor + layer, err := ctx.Layers.Layer("test-layer") + Expect(err).NotTo(HaveOccurred()) + + layer, err = n.Contribute(layer) + Expect(err).NotTo(HaveOccurred()) + + Expect(layer.Launch).To(BeTrue()) + + execution := executor.Calls[0].Arguments[0].(effect.Execution) + Expect(execution.Command).To(Equal("npm")) + Expect(execution.Args).To(Equal([]string{"install", "--no-save", + filepath.Join("testdata", + "eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138", + "stub-cloud-profiler-agent.tgz"), + })) + + Expect(layer.LaunchEnvironment[fmt.Sprintf("%s.default", profiler.NodePath)]). + To(Equal(filepath.Join(layer.Path, "node_modules"))) + }) + }) + + context("Launch", func() { + var ( + l = profiler.NodeJSLaunch{ + CredentialSource: common.MetadataServer, + } + ) + + it.Before(func() { + var err error + l.ApplicationPath, err = ioutil.TempDir("", "profiler-nodejs-launch-application") + Expect(err).NotTo(HaveOccurred()) + + Expect(ioutil.WriteFile(filepath.Join(l.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), 0644)). + To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(l.ApplicationPath, "main.js"), []byte("test"), 0644)). + To(Succeed()) + + Expect(os.Setenv(profiler.NodePath, "test-path")).To(Succeed()) + Expect(os.Setenv(common.Module, "test-module")).To(Succeed()) + Expect(os.Setenv(common.Version, "test-version")).To(Succeed()) + }) + + it.After(func() { + Expect(os.RemoveAll(l.ApplicationPath)).To(Succeed()) + + Expect(os.Unsetenv(profiler.NodePath)).To(Succeed()) + Expect(os.Unsetenv(common.Module)).To(Succeed()) + Expect(os.Unsetenv(common.Version)).To(Succeed()) + }) + + it("does not contribute if source is None", func() { + l.CredentialSource = common.None + + Expect(l.Execute()).To(BeNil()) + }) + + it("returns error if BPI_GOOGLE_CLOUD_PROFILER_NODE_PATH is not set", func() { + Expect(os.Unsetenv(profiler.NodePath)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPI_GOOGLE_CLOUD_PROFILER_NODE_PATH must be set")) + }) + + it("returns error if BPL_GOOGLE_CLOUD_MODULE is not set", func() { + Expect(os.Unsetenv(common.Module)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPL_GOOGLE_CLOUD_MODULE must be set")) + }) + + it("returns error if BPL_GOOGLE_CLOUD_VERSION is not set", func() { + Expect(os.Unsetenv(common.Version)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPL_GOOGLE_CLOUD_VERSION must be set")) + }) + + context("binding", func() { + + it.Before(func() { + l.CredentialSource = common.Binding + Expect(os.Setenv(common.ProjectID, "test-project-id")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv(common.ProjectID)).To(Succeed()) + }) + + it("returns error if BPL_GOOGLE_CLOUD_PROJECT_ID is not set", func() { + Expect(os.Unsetenv(common.ProjectID)).To(Succeed()) + + _, err := l.Execute() + Expect(err).To(MatchError("$BPL_GOOGLE_CLOUD_PROJECT_ID must be set")) + }) + + context("@google-cloud/profiler already required", func() { + + it.Before(func() { + Expect(ioutil.WriteFile(filepath.Join(l.ApplicationPath, "main.js"), []byte(`test +require('@google-cloud/profiler').start() +test`), 0644)). + To(Succeed()) + }) + + it("does not contribute require('@google-cloud/profiler')", func() { + _, err := l.Execute() + Expect(err).NotTo(HaveOccurred()) + + b, err := ioutil.ReadFile(filepath.Join(l.ApplicationPath, "main.js")) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(b)).To(Equal(`test +require('@google-cloud/profiler').start() +test`)) + }) + }) + + context("@google-cloud/profiler not already required", func() { + + it("contributes require('@google-cloud/profiler')", func() { + _, err := l.Execute() + Expect(err).NotTo(HaveOccurred()) + + b, err := ioutil.ReadFile(filepath.Join(l.ApplicationPath, "main.js")) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(b)).To(Equal(`require('@google-cloud/profiler').start({ + projectId: 'test-project-id', + serviceContext: { + service: 'test-module', + version: 'test-version', + }, +}); +test`)) + }) + }) + + it("contributes NODE_PATH", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "NODE_PATH": "test-path", + })) + }) + }) + + context("metadata server", func() { + + context("@google-cloud/profiler already required", func() { + + it.Before(func() { + Expect(ioutil.WriteFile(filepath.Join(l.ApplicationPath, "main.js"), []byte(`test +require('@google-cloud/profiler').start() +test`), 0644)). + To(Succeed()) + }) + + it("does not contribute require('@google-cloud/profiler')", func() { + _, err := l.Execute() + Expect(err).NotTo(HaveOccurred()) + + b, err := ioutil.ReadFile(filepath.Join(l.ApplicationPath, "main.js")) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(b)).To(Equal(`test +require('@google-cloud/profiler').start() +test`)) + }) + }) + + context("@google-cloud/profiler not already required", func() { + + it("contributes require('@google-cloud/profiler')", func() { + _, err := l.Execute() + Expect(err).NotTo(HaveOccurred()) + + b, err := ioutil.ReadFile(filepath.Join(l.ApplicationPath, "main.js")) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(b)).To(Equal(`require('@google-cloud/profiler').start({ + serviceContext: { + service: 'test-module', + version: 'test-version', + }, +}); +test`)) + }) + }) + + it("contributes NODE_PATH", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "NODE_PATH": "test-path", + })) + }) + }) + + context("existing $NODE_PATH", func() { + + it.Before(func() { + Expect(os.Setenv("NODE_PATH", "test-node-path")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("NODE_PATH")).To(Succeed()) + }) + + it("contributes NODE_PATH", func() { + Expect(l.Execute()).To(Equal(map[string]string{ + "NODE_PATH": strings.Join([]string{ + "test-node-path", + "test-path", + }, string(os.PathListSeparator)), + })) + }) + }) + }) +} diff --git a/stackdriver/testdata/a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797.toml b/profiler/testdata/a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797.toml similarity index 53% rename from stackdriver/testdata/a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797.toml rename to profiler/testdata/a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797.toml index c92106b..01af35b 100644 --- a/stackdriver/testdata/a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797.toml +++ b/profiler/testdata/a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797.toml @@ -1,2 +1,2 @@ -uri = "https://localhost/stub-stackdriver-profiler-agent.tar.gz" +uri = "https://localhost/stub-cloud-profiler-agent.tar.gz" sha256 = "a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797" diff --git a/stackdriver/testdata/a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797/stub-stackdriver-profiler-agent.tar.gz b/profiler/testdata/a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797/stub-cloud-profiler-agent.tar.gz similarity index 100% rename from stackdriver/testdata/a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797/stub-stackdriver-profiler-agent.tar.gz rename to profiler/testdata/a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797/stub-cloud-profiler-agent.tar.gz diff --git a/stackdriver/testdata/eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138.toml b/profiler/testdata/eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138.toml similarity index 55% rename from stackdriver/testdata/eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138.toml rename to profiler/testdata/eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138.toml index c3609f3..73ba049 100644 --- a/stackdriver/testdata/eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138.toml +++ b/profiler/testdata/eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138.toml @@ -1,2 +1,2 @@ -uri = "https://localhost/stub-stackdriver-profiler-agent.tgz" +uri = "https://localhost/stub-cloud-profiler-agent.tgz" sha256 = "eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138" diff --git a/stackdriver/testdata/eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138/stub-stackdriver-profiler-agent.tgz b/profiler/testdata/eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138/stub-cloud-profiler-agent.tgz similarity index 100% rename from stackdriver/testdata/eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138/stub-stackdriver-profiler-agent.tgz rename to profiler/testdata/eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138/stub-cloud-profiler-agent.tgz diff --git a/scripts/build.sh b/scripts/build.sh index c263b70..6dd7366 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -2,8 +2,8 @@ set -euo pipefail -GOOS="linux" go build -ldflags='-s -w' -o bin/helper github.com/paketo-buildpacks/google-stackdriver/cmd/helper -GOOS="linux" go build -ldflags='-s -w' -o bin/main github.com/paketo-buildpacks/google-stackdriver/cmd/main +GOOS="linux" go build -ldflags='-s -w' -o bin/helper github.com/paketo-buildpacks/google-cloud/cmd/helper +GOOS="linux" go build -ldflags='-s -w' -o bin/main github.com/paketo-buildpacks/google-cloud/cmd/main strip bin/helper bin/main upx -q -9 bin/helper bin/main diff --git a/stackdriver/detect.go b/stackdriver/detect.go deleted file mode 100644 index 57ee547..0000000 --- a/stackdriver/detect.go +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package stackdriver - -import ( - "fmt" - - "github.com/buildpacks/libcnb" - "github.com/paketo-buildpacks/libpak/bindings" -) - -type Detect struct{} - -func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) { - result := libcnb.DetectResult{Pass: false} - - if _, ok, err := bindings.ResolveOne(context.Platform.Bindings, bindings.OfType("StackdriverDebugger")); err != nil { - return libcnb.DetectResult{}, fmt.Errorf("unable to resolve binding StackdriverDebugger\n%w", err) - } else if ok { - result.Pass = true - result.Plans = append(result.Plans, - libcnb.BuildPlan{ - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-debugger-java"}, - }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-debugger-java"}, - {Name: "jvm-application"}, - }, - }, - libcnb.BuildPlan{ - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-debugger-nodejs"}, - }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-debugger-nodejs"}, - {Name: "node", Metadata: map[string]interface{}{"build": true}}, - }, - }, - ) - } - - if _, ok, err := bindings.ResolveOne(context.Platform.Bindings, bindings.OfType("StackdriverProfiler")); err != nil { - return libcnb.DetectResult{}, fmt.Errorf("unable to resolve binding StackdriverProfiler\n%w", err) - } else if ok { - result.Pass = true - result.Plans = append(result.Plans, - libcnb.BuildPlan{ - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-profiler-java"}, - }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-profiler-java"}, - {Name: "jvm-application"}, - }, - }, - libcnb.BuildPlan{ - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-profiler-nodejs"}, - }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-profiler-nodejs"}, - {Name: "node", Metadata: map[string]interface{}{"build": true}}, - }, - }, - ) - } - - return result, nil -} diff --git a/stackdriver/detect_test.go b/stackdriver/detect_test.go deleted file mode 100644 index d599e8e..0000000 --- a/stackdriver/detect_test.go +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package stackdriver_test - -import ( - "testing" - - "github.com/buildpacks/libcnb" - . "github.com/onsi/gomega" - "github.com/sclevine/spec" - - "github.com/paketo-buildpacks/google-stackdriver/stackdriver" -) - -func testDetect(t *testing.T, context spec.G, it spec.S) { - var ( - Expect = NewWithT(t).Expect - - ctx libcnb.DetectContext - detect stackdriver.Detect - ) - - it("fails without service", func() { - Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{})) - }) - - it("passes with debugger service", func() { - ctx.Platform.Bindings = libcnb.Bindings{ - libcnb.Binding{Name: "test-service", Type: "StackdriverDebugger"}, - } - - Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{ - Pass: true, - Plans: []libcnb.BuildPlan{ - { - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-debugger-java"}, - }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-debugger-java"}, - {Name: "jvm-application"}, - }, - }, - { - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-debugger-nodejs"}, - }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-debugger-nodejs"}, - {Name: "node", Metadata: map[string]interface{}{"build": true}}, - }, - }, - }, - })) - }) - - it("passes with profiler service", func() { - ctx.Platform.Bindings = libcnb.Bindings{ - libcnb.Binding{Name: "test-service", Type: "StackdriverProfiler"}, - } - - Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{ - Pass: true, - Plans: []libcnb.BuildPlan{ - { - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-profiler-java"}, - }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-profiler-java"}, - {Name: "jvm-application"}, - }, - }, - { - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-profiler-nodejs"}, - }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-profiler-nodejs"}, - {Name: "node", Metadata: map[string]interface{}{"build": true}}, - }, - }, - }, - })) - }) -} diff --git a/stackdriver/java_debugger_agent.go b/stackdriver/java_debugger_agent.go deleted file mode 100644 index 3800852..0000000 --- a/stackdriver/java_debugger_agent.go +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package stackdriver - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/buildpacks/libcnb" - "github.com/paketo-buildpacks/libpak" - "github.com/paketo-buildpacks/libpak/bard" - "github.com/paketo-buildpacks/libpak/crush" -) - -type JavaDebuggerAgent struct { - LayerContributor libpak.DependencyLayerContributor - Logger bard.Logger -} - -func NewJavaDebuggerAgent(dependency libpak.BuildpackDependency, cache libpak.DependencyCache, plan *libcnb.BuildpackPlan) JavaDebuggerAgent { - return JavaDebuggerAgent{LayerContributor: libpak.NewDependencyLayerContributor(dependency, cache, plan)} -} - -func (j JavaDebuggerAgent) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { - j.LayerContributor.Logger = j.Logger - - return j.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { - j.Logger.Bodyf("Expanding to %s", layer.Path) - - if err := crush.ExtractTarGz(artifact, layer.Path, 0); err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to extract %s\n%w", artifact.Name(), err) - } - - layer.LaunchEnvironment.Appendf("JAVA_TOOL_OPTIONS", " ", - "-agentpath:%s=--logtostderr=1", filepath.Join(layer.Path, "cdbg_java_agent.so")) - - return layer, nil - }, libpak.LaunchLayer) -} - -func (j JavaDebuggerAgent) Name() string { - return j.LayerContributor.LayerName() -} diff --git a/stackdriver/java_debugger_agent_test.go b/stackdriver/java_debugger_agent_test.go deleted file mode 100644 index 84c199c..0000000 --- a/stackdriver/java_debugger_agent_test.go +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package stackdriver_test - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/buildpacks/libcnb" - . "github.com/onsi/gomega" - "github.com/paketo-buildpacks/libpak" - "github.com/sclevine/spec" - - "github.com/paketo-buildpacks/google-stackdriver/stackdriver" -) - -func testJavaDebuggerAgent(t *testing.T, context spec.G, it spec.S) { - var ( - Expect = NewWithT(t).Expect - - ctx libcnb.BuildContext - ) - - it.Before(func() { - var err error - - ctx.Layers.Path, err = ioutil.TempDir("", "java-debugger-agent-layers") - Expect(err).NotTo(HaveOccurred()) - }) - - it.After(func() { - Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) - }) - - it("contributes Java agent", func() { - dep := libpak.BuildpackDependency{ - URI: "https://localhost/stub-stackdriver-debugger-agent.tar.gz", - SHA256: "80ceb691b8b586e15dedae62564dea2cfe8e2f6ac44ec48fe4dc87599fa22cab", - } - dc := libpak.DependencyCache{CachePath: "testdata"} - - layer, err := ctx.Layers.Layer("test-layer") - Expect(err).NotTo(HaveOccurred()) - - layer, err = stackdriver.NewJavaDebuggerAgent(dep, dc, &libcnb.BuildpackPlan{}).Contribute(layer) - Expect(err).NotTo(HaveOccurred()) - - Expect(layer.Launch).To(BeTrue()) - - file := filepath.Join(layer.Path, "cdbg_java_agent.so") - Expect(file).To(BeARegularFile()) - Expect(layer.LaunchEnvironment["JAVA_TOOL_OPTIONS.delim"]).To(Equal(" ")) - Expect(layer.LaunchEnvironment["JAVA_TOOL_OPTIONS.append"]).To(Equal(fmt.Sprintf("-agentpath:%s=--logtostderr=1", file))) - }) -} diff --git a/stackdriver/java_profiler_agent.go b/stackdriver/java_profiler_agent.go deleted file mode 100644 index b888bc6..0000000 --- a/stackdriver/java_profiler_agent.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package stackdriver - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/buildpacks/libcnb" - "github.com/paketo-buildpacks/libpak" - "github.com/paketo-buildpacks/libpak/bard" - "github.com/paketo-buildpacks/libpak/crush" -) - -type JavaProfilerAgent struct { - LayerContributor libpak.DependencyLayerContributor - Logger bard.Logger -} - -func NewJavaProfilerAgent(dependency libpak.BuildpackDependency, cache libpak.DependencyCache, plan *libcnb.BuildpackPlan) JavaProfilerAgent { - return JavaProfilerAgent{LayerContributor: libpak.NewDependencyLayerContributor(dependency, cache, plan)} -} - -func (j JavaProfilerAgent) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { - j.LayerContributor.Logger = j.Logger - - return j.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { - j.Logger.Bodyf("Expanding to %s", layer.Path) - - if err := crush.ExtractTarGz(artifact, layer.Path, 0); err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to extract %s\n%w", artifact.Name(), err) - } - - layer.LaunchEnvironment.Default("BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH", - filepath.Join(layer.Path, "profiler_java_agent.so")) - - return layer, nil - }, libpak.LaunchLayer) - -} - -func (j JavaProfilerAgent) Name() string { - return j.LayerContributor.LayerName() -} diff --git a/stackdriver/java_profiler_agent_test.go b/stackdriver/java_profiler_agent_test.go deleted file mode 100644 index 77849b6..0000000 --- a/stackdriver/java_profiler_agent_test.go +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package stackdriver_test - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/buildpacks/libcnb" - . "github.com/onsi/gomega" - "github.com/paketo-buildpacks/libpak" - "github.com/sclevine/spec" - - "github.com/paketo-buildpacks/google-stackdriver/stackdriver" -) - -func testJavaProfilerAgent(t *testing.T, context spec.G, it spec.S) { - var ( - Expect = NewWithT(t).Expect - - ctx libcnb.BuildContext - ) - - it.Before(func() { - var err error - - ctx.Layers.Path, err = ioutil.TempDir("", "java-profiler-agent-layers") - Expect(err).NotTo(HaveOccurred()) - }) - - it.After(func() { - Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) - }) - - it("contributes Java agent", func() { - dep := libpak.BuildpackDependency{ - URI: "https://localhost/stub-stackdriver-profiler-agent.tar.gz", - SHA256: "a27bbf74fa913fe70273c38831bc1043a2da0e28766423b81d8e9a042b353797", - } - dc := libpak.DependencyCache{CachePath: "testdata"} - - layer, err := ctx.Layers.Layer("test-layer") - Expect(err).NotTo(HaveOccurred()) - - layer, err = stackdriver.NewJavaProfilerAgent(dep, dc, &libcnb.BuildpackPlan{}).Contribute(layer) - Expect(err).NotTo(HaveOccurred()) - - Expect(layer.Launch).To(BeTrue()) - - file := filepath.Join(layer.Path, "profiler_java_agent.so") - Expect(file).To(BeARegularFile()) - Expect(layer.LaunchEnvironment["BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH.default"]).To(Equal(file)) - }) -} diff --git a/stackdriver/nodejs_debugger_agent.go b/stackdriver/nodejs_debugger_agent.go deleted file mode 100644 index 9e30d09..0000000 --- a/stackdriver/nodejs_debugger_agent.go +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package stackdriver - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "regexp" - - "github.com/buildpacks/libcnb" - "github.com/paketo-buildpacks/libpak" - "github.com/paketo-buildpacks/libpak/bard" - "github.com/paketo-buildpacks/libpak/effect" - "github.com/paketo-buildpacks/libpak/sherpa" -) - -type NodeJSDebuggerAgent struct { - ApplicationPath string - Executor effect.Executor - LayerContributor libpak.DependencyLayerContributor - Logger bard.Logger -} - -func NewNodeJSDebuggerAgent(applicationPath string, dependency libpak.BuildpackDependency, cache libpak.DependencyCache, plan *libcnb.BuildpackPlan) NodeJSDebuggerAgent { - return NodeJSDebuggerAgent{ - ApplicationPath: applicationPath, - Executor: effect.NewExecutor(), - LayerContributor: libpak.NewDependencyLayerContributor(dependency, cache, plan), - } -} - -func (n NodeJSDebuggerAgent) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { - n.LayerContributor.Logger = n.Logger - - layer, err := n.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { - n.Logger.Bodyf("Installing to %s", layer.Path) - - if err := n.Executor.Execute(effect.Execution{ - Command: "npm", - Args: []string{"install", "--no-save", artifact.Name()}, - Dir: layer.Path, - Stdout: n.Logger.InfoWriter(), - Stderr: n.Logger.InfoWriter(), - }); err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to run npm install\n%w", err) - } - - layer.LaunchEnvironment.Prepend("NODE_PATH", string(os.PathListSeparator), filepath.Join(layer.Path, "node_modules")) - - return layer, nil - }, libpak.LaunchLayer) - if err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to install node module\n%w", err) - } - - m, err := sherpa.NodeJSMainModule(n.ApplicationPath) - if err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to find main module in %s\n%w", n.ApplicationPath, err) - } - - file := filepath.Join(n.ApplicationPath, m) - c, err := ioutil.ReadFile(file) - if err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to read contents of %s\n%w", file, err) - } - - if !regexp.MustCompile(`require\(['"]@google-cloud/debug-agent['"]\)`).Match(c) { - n.Logger.Header("Requiring '@google-cloud/debug-agent' module") - - if err := ioutil.WriteFile(file, append([]byte("require('@google-cloud/debug-agent').start();\n"), c...), 0644); err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to write main module %s\n%w", file, err) - } - } - - return layer, nil -} - -func (n NodeJSDebuggerAgent) Name() string { - return n.LayerContributor.LayerName() -} diff --git a/stackdriver/nodejs_debugger_agent_test.go b/stackdriver/nodejs_debugger_agent_test.go deleted file mode 100644 index 2a1f6d1..0000000 --- a/stackdriver/nodejs_debugger_agent_test.go +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package stackdriver_test - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/buildpacks/libcnb" - . "github.com/onsi/gomega" - "github.com/paketo-buildpacks/libpak" - "github.com/paketo-buildpacks/libpak/effect" - "github.com/paketo-buildpacks/libpak/effect/mocks" - "github.com/sclevine/spec" - "github.com/stretchr/testify/mock" - - "github.com/paketo-buildpacks/google-stackdriver/stackdriver" -) - -func testNodeJSDebuggerAgent(t *testing.T, context spec.G, it spec.S) { - var ( - Expect = NewWithT(t).Expect - - ctx libcnb.BuildContext - executor *mocks.Executor - ) - - it.Before(func() { - var err error - - ctx.Application.Path, err = ioutil.TempDir("", "nodejs-debugger-agent-application") - Expect(err).NotTo(HaveOccurred()) - - ctx.Layers.Path, err = ioutil.TempDir("", "nodejs-debugger-agent-layers") - Expect(err).NotTo(HaveOccurred()) - - executor = &mocks.Executor{} - executor.On("Execute", mock.Anything).Return(nil) - }) - - it.After(func() { - Expect(os.RemoveAll(ctx.Application.Path)).To(Succeed()) - Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) - }) - - it("contributes NodeJS agent", func() { - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "package.json"), []byte(`{ "main": "main.js" }`), - 0644)).To(Succeed()) - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "main.js"), []byte{}, 0644)).To(Succeed()) - - dep := libpak.BuildpackDependency{ - URI: "https://localhost/stub-stackdriver-debugger-agent.tgz", - SHA256: "c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6", - } - dc := libpak.DependencyCache{CachePath: "testdata"} - - n := stackdriver.NewNodeJSDebuggerAgent(ctx.Application.Path, dep, dc, &libcnb.BuildpackPlan{}) - n.Executor = executor - layer, err := ctx.Layers.Layer("test-layer") - Expect(err).NotTo(HaveOccurred()) - - layer, err = n.Contribute(layer) - Expect(err).NotTo(HaveOccurred()) - - Expect(layer.Launch).To(BeTrue()) - - execution := executor.Calls[0].Arguments[0].(effect.Execution) - Expect(execution.Command).To(Equal("npm")) - Expect(execution.Args).To(Equal([]string{"install", "--no-save", - filepath.Join("testdata", - "c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6", - "stub-stackdriver-debugger-agent.tgz"), - })) - - Expect(layer.LaunchEnvironment["NODE_PATH.delim"]).To(Equal(string(os.PathListSeparator))) - Expect(layer.LaunchEnvironment["NODE_PATH.prepend"]).To(Equal(filepath.Join(layer.Path, "node_modules"))) - }) - - it("requires @google-cloud/debug-agent module", func() { - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "package.json"), []byte(`{ "main": "main.js" }`), - 0644)).To(Succeed()) - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "main.js"), []byte("test"), 0644)).To(Succeed()) - - dep := libpak.BuildpackDependency{ - URI: "https://localhost/stub-stackdriver-debugger-agent.tgz", - SHA256: "c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6", - } - dc := libpak.DependencyCache{CachePath: "testdata"} - - n := stackdriver.NewNodeJSDebuggerAgent(ctx.Application.Path, dep, dc, &libcnb.BuildpackPlan{}) - n.Executor = executor - layer, err := ctx.Layers.Layer("test-layer") - Expect(err).NotTo(HaveOccurred()) - - layer, err = n.Contribute(layer) - Expect(err).NotTo(HaveOccurred()) - - Expect(ioutil.ReadFile(filepath.Join(ctx.Application.Path, "main.js"))).To(Equal( - []byte("require('@google-cloud/debug-agent').start();\ntest"))) - }) - - it("does not require @google-cloud/debug-agent module", func() { - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "package.json"), []byte(`{ "main": "main.js" }`), - 0644)).To(Succeed()) - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "main.js"), - []byte("test\nrequire('@google-cloud/debug-agent')\ntest"), 0644)).To(Succeed()) - - dep := libpak.BuildpackDependency{ - URI: "https://localhost/stub-stackdriver-debugger-agent.tgz", - SHA256: "c3ecfa1e2daa29db419b063dec9ea20108923e406d9ab7a35318f6f14f615dc6", - } - dc := libpak.DependencyCache{CachePath: "testdata"} - - n := stackdriver.NewNodeJSDebuggerAgent(ctx.Application.Path, dep, dc, &libcnb.BuildpackPlan{}) - n.Executor = executor - layer, err := ctx.Layers.Layer("test-layer") - Expect(err).NotTo(HaveOccurred()) - - layer, err = n.Contribute(layer) - Expect(err).NotTo(HaveOccurred()) - - Expect(ioutil.ReadFile(filepath.Join(ctx.Application.Path, "main.js"))).To(Equal( - []byte("test\nrequire('@google-cloud/debug-agent')\ntest"))) - }) -} diff --git a/stackdriver/nodejs_profiler_agent.go b/stackdriver/nodejs_profiler_agent.go deleted file mode 100644 index a156251..0000000 --- a/stackdriver/nodejs_profiler_agent.go +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package stackdriver - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "regexp" - - "github.com/buildpacks/libcnb" - "github.com/paketo-buildpacks/libpak" - "github.com/paketo-buildpacks/libpak/bard" - "github.com/paketo-buildpacks/libpak/effect" - "github.com/paketo-buildpacks/libpak/sherpa" -) - -type NodeJSProfilerAgent struct { - ApplicationPath string - Executor effect.Executor - LayerContributor libpak.DependencyLayerContributor - Logger bard.Logger -} - -func NewNodeJSProfilerAgent(applicationPath string, dependency libpak.BuildpackDependency, cache libpak.DependencyCache, plan *libcnb.BuildpackPlan) NodeJSProfilerAgent { - return NodeJSProfilerAgent{ - ApplicationPath: applicationPath, - Executor: effect.NewExecutor(), - LayerContributor: libpak.NewDependencyLayerContributor(dependency, cache, plan), - } -} - -func (n NodeJSProfilerAgent) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { - n.LayerContributor.Logger = n.Logger - - layer, err := n.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { - n.Logger.Bodyf("Installing to %s", layer.Path) - - if err := n.Executor.Execute(effect.Execution{ - Command: "npm", - Args: []string{"install", "--no-save", artifact.Name()}, - Dir: layer.Path, - Stdout: n.Logger.InfoWriter(), - Stderr: n.Logger.InfoWriter(), - }); err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to run npm install\n%w", err) - } - - layer.LaunchEnvironment.Prepend("NODE_PATH", string(os.PathListSeparator), filepath.Join(layer.Path, "node_modules")) - - return layer, nil - }, libpak.LaunchLayer) - if err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to install node module\n%w", err) - } - - m, err := sherpa.NodeJSMainModule(n.ApplicationPath) - if err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to find main module in %s\n%w", n.ApplicationPath, err) - } - - file := filepath.Join(n.ApplicationPath, m) - c, err := ioutil.ReadFile(file) - if err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to read contents of %s\n%w", file, err) - } - - if !regexp.MustCompile(`require\(['"]@google-cloud/profiler['"]\)`).Match(c) { - n.Logger.Header("Requiring '@google-cloud/profiler' module") - - if err := ioutil.WriteFile(file, append([]byte("require('@google-cloud/profiler').start();\n"), c...), 0644); err != nil { - return libcnb.Layer{}, fmt.Errorf("unable to write main module %s\n%w", file, err) - } - } - - return layer, nil -} - -func (n NodeJSProfilerAgent) Name() string { - return n.LayerContributor.LayerName() -} diff --git a/stackdriver/nodejs_profiler_agent_test.go b/stackdriver/nodejs_profiler_agent_test.go deleted file mode 100644 index 9fc7638..0000000 --- a/stackdriver/nodejs_profiler_agent_test.go +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2018-2020 the original author or authors. - * - * 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 - * - * https://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. - */ - -package stackdriver_test - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/buildpacks/libcnb" - . "github.com/onsi/gomega" - "github.com/paketo-buildpacks/libpak" - "github.com/paketo-buildpacks/libpak/effect" - "github.com/paketo-buildpacks/libpak/effect/mocks" - "github.com/sclevine/spec" - "github.com/stretchr/testify/mock" - - "github.com/paketo-buildpacks/google-stackdriver/stackdriver" -) - -func testNodeJSProfilerAgent(t *testing.T, context spec.G, it spec.S) { - var ( - Expect = NewWithT(t).Expect - - ctx libcnb.BuildContext - executor *mocks.Executor - ) - - it.Before(func() { - var err error - - ctx.Application.Path, err = ioutil.TempDir("", "nodejs-profiler-agent-application") - Expect(err).NotTo(HaveOccurred()) - - ctx.Layers.Path, err = ioutil.TempDir("", "nodejs-profiler-agent-layers") - Expect(err).NotTo(HaveOccurred()) - - executor = &mocks.Executor{} - executor.On("Execute", mock.Anything).Return(nil) - }) - - it.After(func() { - Expect(os.RemoveAll(ctx.Application.Path)).To(Succeed()) - Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) - }) - - it("contributes NodeJS agent", func() { - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "package.json"), []byte(`{ "main": "main.js" }`), - 0644)).To(Succeed()) - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "main.js"), []byte{}, 0644)).To(Succeed()) - - dep := libpak.BuildpackDependency{ - URI: "https://localhost/stub-stackdriver-profiler-agent.tgz", - SHA256: "eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138", - } - dc := libpak.DependencyCache{CachePath: "testdata"} - - n := stackdriver.NewNodeJSProfilerAgent(ctx.Application.Path, dep, dc, &libcnb.BuildpackPlan{}) - n.Executor = executor - layer, err := ctx.Layers.Layer("test-layer") - Expect(err).NotTo(HaveOccurred()) - - layer, err = n.Contribute(layer) - Expect(err).NotTo(HaveOccurred()) - - Expect(layer.Launch).To(BeTrue()) - - execution := executor.Calls[0].Arguments[0].(effect.Execution) - Expect(execution.Command).To(Equal("npm")) - Expect(execution.Args).To(Equal([]string{"install", "--no-save", - filepath.Join("testdata", - "eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138", - "stub-stackdriver-profiler-agent.tgz"), - })) - - Expect(layer.LaunchEnvironment["NODE_PATH.delim"]).To(Equal(string(os.PathListSeparator))) - Expect(layer.LaunchEnvironment["NODE_PATH.prepend"]).To(Equal(filepath.Join(layer.Path, "node_modules"))) - }) - - it("requires @google-cloud/profiler module", func() { - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "package.json"), []byte(`{ "main": "main.js" }`), - 0644)).To(Succeed()) - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "main.js"), []byte("test"), 0644)).To(Succeed()) - - dep := libpak.BuildpackDependency{ - URI: "https://localhost/stub-stackdriver-profiler-agent.tgz", - SHA256: "eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138", - } - dc := libpak.DependencyCache{CachePath: "testdata"} - - n := stackdriver.NewNodeJSProfilerAgent(ctx.Application.Path, dep, dc, &libcnb.BuildpackPlan{}) - n.Executor = executor - layer, err := ctx.Layers.Layer("test-layer") - Expect(err).NotTo(HaveOccurred()) - - layer, err = n.Contribute(layer) - Expect(err).NotTo(HaveOccurred()) - - Expect(ioutil.ReadFile(filepath.Join(ctx.Application.Path, "main.js"))).To(Equal( - []byte("require('@google-cloud/profiler').start();\ntest"))) - }) - - it("does not require @google-cloud/profiler module", func() { - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "package.json"), []byte(`{ "main": "main.js" }`), - 0644)).To(Succeed()) - Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "main.js"), - []byte("test\nrequire('@google-cloud/profiler')\ntest"), 0644)).To(Succeed()) - - dep := libpak.BuildpackDependency{ - URI: "https://localhost/stub-stackdriver-profiler-agent.tgz", - SHA256: "eee0eca3815f2d4aeaa7e23c1150878ee42864d26ec7331d800b34e667714138", - } - dc := libpak.DependencyCache{CachePath: "testdata"} - - n := stackdriver.NewNodeJSProfilerAgent(ctx.Application.Path, dep, dc, &libcnb.BuildpackPlan{}) - n.Executor = executor - layer, err := ctx.Layers.Layer("test-layer") - Expect(err).NotTo(HaveOccurred()) - - layer, err = n.Contribute(layer) - Expect(err).NotTo(HaveOccurred()) - - Expect(ioutil.ReadFile(filepath.Join(ctx.Application.Path, "main.js"))).To(Equal( - []byte("test\nrequire('@google-cloud/profiler')\ntest"))) - }) -}