diff --git a/README.md b/README.md index 24ca96c..f5f541d 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ The Paketo Google Stackdriver Buildpack is a Cloud Native Buildpack that contrib ## 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` +* `$BP_GOOGLE_STACKDRIVER_DEBUGGER_ENABLED` is set +* `$BP_GOOGLE_STACKDRIVER_PROFILER_ENABLED` is set The buildpack will do the following for Java applications: diff --git a/buildpack.toml b/buildpack.toml index a85dbce..a14c3a2 100644 --- a/buildpack.toml +++ b/buildpack.toml @@ -42,6 +42,16 @@ name = "BPL_GOOGLE_STACKDRIVER_VERSION" description = "the version of the application" launch = true +[[metadata.configurations]] +name = "BP_GOOGLE_STACKDRIVER_DEBUGGER_ENABLED" +description = "whether to contribute Google Stackdriver Debugger support" +build = true + +[[metadata.configurations]] +name = "BP_GOOGLE_STACKDRIVER_PROFILER_ENABLED" +description = "whether to contribute Google Stackdriver Profiler support" +build = true + [[metadata.dependencies]] id = "google-stackdriver-debugger-java" name = "Google Stackdriver Debugger Java Agent" diff --git a/cmd/helper/main.go b/cmd/helper/main.go index b04e90b..afc0798 100644 --- a/cmd/helper/main.go +++ b/cmd/helper/main.go @@ -21,6 +21,7 @@ import ( "os" "github.com/buildpacks/libcnb" + "github.com/paketo-buildpacks/libpak/bard" "github.com/paketo-buildpacks/libpak/sherpa" @@ -29,23 +30,24 @@ import ( 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} - ) - - c.Bindings, err = libcnb.NewBindingsFromEnvironment() + l := bard.NewLogger(os.Stdout) + + a, err := os.Getwd() + if err != nil { + return fmt.Errorf("unable to read working directory\n%w", err) + } + + b, err := libcnb.NewBindingsFromEnvironment() if err != nil { return fmt.Errorf("unable to read bindings from environment\n%w", err) } return sherpa.Helpers(map[string]sherpa.ExecD{ - "credentials": c, - "java-debugger": d, - "java-profiler": p, + "credentials": helper.Credentials{Bindings: b, Logger: l}, + "java-debugger": helper.JavaDebugger{Bindings: b, Logger: l, InGCP: helper.InGCP}, + "java-profiler": helper.JavaProfiler{Bindings: b, Logger: l, InGCP: helper.InGCP}, + "nodejs-debugger": helper.NodeJSDebugger{ApplicationPath: a, Bindings: b, Logger: l, InGCP: helper.InGCP}, + "nodejs-profiler": helper.NodeJSProfiler{ApplicationPath: a, Bindings: b, Logger: l, InGCP: helper.InGCP}, }) }) } diff --git a/go.mod b/go.mod index 1c0a925..265388a 100644 --- a/go.mod +++ b/go.mod @@ -9,3 +9,5 @@ require ( github.com/sclevine/spec v1.4.0 github.com/stretchr/testify v1.6.1 ) + +replace github.com/paketo-buildpacks/libpak => ../../paketo-buildpacks/libpak diff --git a/helper/credentials.go b/helper/credentials.go index add0cc6..6d667cb 100644 --- a/helper/credentials.go +++ b/helper/credentials.go @@ -17,9 +17,8 @@ package helper import ( - "fmt" - "github.com/buildpacks/libcnb" + "github.com/paketo-buildpacks/libpak/bard" "github.com/paketo-buildpacks/libpak/bindings" ) @@ -30,19 +29,10 @@ type Credentials struct { } 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 - } - } + b := bindings.Resolve(c.Bindings, bindings.Or(bindings.OfType("StackdriverDebugger"), bindings.OfType("StackdriverProfiler"))) - 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 { + if len(b) > 0 { + if p, ok := b[0].SecretFilePath("ApplicationCredentials"); ok { c.Logger.Info("Configuring Google application credentials") return map[string]string{"GOOGLE_APPLICATION_CREDENTIALS": p}, nil } diff --git a/helper/credentials_test.go b/helper/credentials_test.go index c53b1ad..397e05f 100644 --- a/helper/credentials_test.go +++ b/helper/credentials_test.go @@ -34,7 +34,7 @@ func testCredentials(t *testing.T, context spec.G, it spec.S) { ) it("does not contribute properties if no binding exists", func() { - Expect(c.Execute()).To(BeZero()) + Expect(c.Execute()).To(BeNil()) }) it("contributes credentials if debugger binding exists", func() { diff --git a/helper/in_gcp.go b/helper/in_gcp.go new file mode 100644 index 0000000..2257449 --- /dev/null +++ b/helper/in_gcp.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 helper + +import ( + "net/http" +) + +// InGCP detects whether the application is running within GCP. Detection is based on the existence of the metadata +// server as defined in https://cloud.google.com/compute/docs/storing-retrieving-metadata#querying. +func InGCP() bool { + 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 := http.DefaultClient.Do(req) + if err != nil { + return false + } + defer resp.Body.Close() + + return true +} diff --git a/helper/init_test.go b/helper/init_test.go index 6c140da..7e5464f 100644 --- a/helper/init_test.go +++ b/helper/init_test.go @@ -28,5 +28,7 @@ func TestUnit(t *testing.T) { suite("Credentials", testCredentials) suite("JavaDebugger", testJavaDebugger) suite("JavaProfiler", testJavaProfiler) + suite("NodeJSDebugger", testNodeJSDebugger) + suite("NodeJSProfiler", testNodeJSProfiler) suite.Run(t) } diff --git a/helper/java_debugger.go b/helper/java_debugger.go index acbfbe2..920100d 100644 --- a/helper/java_debugger.go +++ b/helper/java_debugger.go @@ -19,42 +19,61 @@ package helper import ( "fmt" "os" - "strings" + + "github.com/buildpacks/libcnb" "github.com/paketo-buildpacks/libpak/bard" + "github.com/paketo-buildpacks/libpak/bindings" + "github.com/paketo-buildpacks/libpak/sherpa" ) type JavaDebugger struct { - Logger bard.Logger + Bindings libcnb.Bindings + Logger bard.Logger + InGCP func() bool } func (j JavaDebugger) Execute() (map[string]string, error) { - module, ok := os.LookupEnv("BPL_GOOGLE_STACKDRIVER_MODULE") - if !ok { - module = "default-module" + if !j.configured() { + return nil, nil } + module := sherpa.GetEnvWithDefault("BPL_GOOGLE_STACKDRIVER_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) + message := fmt.Sprintf("Google Stackdriver Debugger enabled for %s", module) + if version != "" { + message = fmt.Sprintf("%s:%s", message, version) + } + j.Logger.Info(message) + + agentPath, err := sherpa.GetEnvRequired("BPI_GOOGLE_STACKDRIVER_DEBUGGER_JAVA_AGENT_PATH") + if err != nil { + return nil, err } - values = append(values, + var agent []string + agent = append(agent, + fmt.Sprintf("-agentpath:%s=--logtostderr=1", agentPath), "-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)) + agent = append(agent, 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) + return map[string]string{ + "JAVA_TOOL_OPTIONS": sherpa.AppendToEnvVar("JAVA_TOOL_OPTIONS", " ", agent...), + }, nil +} + +func (j JavaDebugger) configured() bool { + if _, ok, err := bindings.ResolveOne(j.Bindings, bindings.OfType("StackdriverDebugger")); err != nil { + return false + } else if ok { + return true } - j.Logger.Info(message) - return map[string]string{"JAVA_TOOL_OPTIONS": strings.Join(values, " ")}, nil + return j.InGCP() } diff --git a/helper/java_debugger_test.go b/helper/java_debugger_test.go index 45e4bcf..dad165b 100644 --- a/helper/java_debugger_test.go +++ b/helper/java_debugger_test.go @@ -20,6 +20,7 @@ import ( "os" "testing" + "github.com/buildpacks/libcnb" . "github.com/onsi/gomega" "github.com/sclevine/spec" @@ -30,60 +31,104 @@ func testJavaDebugger(t *testing.T, context spec.G, it spec.S) { var ( Expect = NewWithT(t).Expect - j helper.JavaDebugger + j = helper.JavaDebugger{ + InGCP: func() bool { return false }, + } ) - 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", - })) + it("does nothing without binding or GCP", func() { + Expect(j.Execute()).To(BeNil()) }) - context("$BPL_GOOGLE_STACKDRIVER_MODULE", func() { + context("with binding", func() { it.Before(func() { - Expect(os.Setenv("BPL_GOOGLE_STACKDRIVER_MODULE", "test-module")).To(Succeed()) + j.Bindings = libcnb.Bindings{libcnb.Binding{Type: "StackdriverDebugger"}} }) - it.After(func() { - Expect(os.Unsetenv("BPL_GOOGLE_STACKDRIVER_MODULE")).To(Succeed()) + it("fails without $BPI_GOOGLE_STACKDRIVER_DEBUGGER_JAVA_AGENT_PATH", func() { + _, err := j.Execute() + Expect(err).To(MatchError("$BPI_GOOGLE_STACKDRIVER_DEBUGGER_JAVA_AGENT_PATH must be set")) }) - 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("$BPI_GOOGLE_STACKDRIVER_DEBUGGER_JAVA_AGENT_PATH", func() { + + it.Before(func() { + Expect(os.Setenv("BPI_GOOGLE_STACKDRIVER_DEBUGGER_JAVA_AGENT_PATH", "test-path")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("BPI_GOOGLE_STACKDRIVER_DEBUGGER_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 -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": "-agentpath:test-path=--logtostderr=1 -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": "-agentpath:test-path=--logtostderr=1 -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 -agentpath:test-path=--logtostderr=1 -Dcom.google.cdbg.auth.serviceaccount.enable=true -Dcom.google.cdbg.module=default-module", + })) + }) + }) }) }) - context("$BPL_GOOGLE_STACKDRIVER_VERSION", func() { + context("with GCP", 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()) - }) + j.InGCP = func() bool { return true } - 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()) + Expect(os.Setenv("BPI_GOOGLE_STACKDRIVER_DEBUGGER_JAVA_AGENT_PATH", "test-path")).To(Succeed()) }) it.After(func() { - Expect(os.Unsetenv("JAVA_TOOL_OPTIONS")).To(Succeed()) + Expect(os.Unsetenv("BPI_GOOGLE_STACKDRIVER_DEBUGGER_JAVA_AGENT_PATH")).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", - })) + it("configures environment", func() { + Expect(j.Execute()).NotTo(BeNil()) }) }) } diff --git a/helper/java_profiler.go b/helper/java_profiler.go index 8ddfffa..6d55c61 100644 --- a/helper/java_profiler.go +++ b/helper/java_profiler.go @@ -19,33 +19,38 @@ package helper import ( "fmt" "os" - "strings" + + "github.com/buildpacks/libcnb" "github.com/paketo-buildpacks/libpak/bard" + "github.com/paketo-buildpacks/libpak/bindings" + "github.com/paketo-buildpacks/libpak/sherpa" ) type JavaProfiler struct { - Logger bard.Logger + Bindings libcnb.Bindings + Logger bard.Logger + InGCP func() bool } func (j JavaProfiler) Execute() (map[string]string, error) { - module, ok := os.LookupEnv("BPL_GOOGLE_STACKDRIVER_MODULE") - if !ok { - module = "default-module" + if !j.configured() { + return nil, nil } + module := sherpa.GetEnvWithDefault("BPL_GOOGLE_STACKDRIVER_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") + message := fmt.Sprintf("Google Stackdriver Profiler enabled for %s", module) + if version != "" { + message = fmt.Sprintf("%s:%s", message, version) } + j.Logger.Info(message) - var values []string - if s, ok := os.LookupEnv("JAVA_TOOL_OPTIONS"); ok { - values = append(values, s) + agentPath, err := sherpa.GetEnvRequired("BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH") + if err != nil { + return nil, err } agent := fmt.Sprintf("-agentpath:%s=-logtostderr=1,-cprof_project_id=%s,-cprof_service=%s", @@ -53,13 +58,18 @@ func (j JavaProfiler) Execute() (map[string]string, error) { 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) + return map[string]string{ + "JAVA_TOOL_OPTIONS": sherpa.AppendToEnvVar("JAVA_TOOL_OPTIONS", " ", agent), + }, nil +} + +func (j JavaProfiler) configured() bool { + if _, ok, err := bindings.ResolveOne(j.Bindings, bindings.OfType("StackdriverProfiler")); err != nil { + return false + } else if ok { + return true } - j.Logger.Info(message) - return map[string]string{"JAVA_TOOL_OPTIONS": strings.Join(values, " ")}, nil + return j.InGCP() } diff --git a/helper/java_profiler_test.go b/helper/java_profiler_test.go index b9e5db5..dd479b5 100644 --- a/helper/java_profiler_test.go +++ b/helper/java_profiler_test.go @@ -20,6 +20,7 @@ import ( "os" "testing" + "github.com/buildpacks/libcnb" . "github.com/onsi/gomega" "github.com/sclevine/spec" @@ -30,76 +31,105 @@ func testJavaProfiler(t *testing.T, context spec.G, it spec.S) { var ( Expect = NewWithT(t).Expect - j helper.JavaProfiler + j = helper.JavaProfiler{ + InGCP: func() bool { return false }, + } ) - 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")) + it("does nothing without binding or GCP", func() { + Expect(j.Execute()).To(BeNil()) }) - context("$BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH", func() { - + context("with binding", func() { it.Before(func() { - Expect(os.Setenv("BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH", "test-path")).To(Succeed()) + j.Bindings = libcnb.Bindings{libcnb.Binding{Type: "StackdriverProfiler"}} }) - it.After(func() { - Expect(os.Unsetenv("BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH")).To(Succeed()) + 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")) }) - 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("$BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH", func() { - context("$BPL_GOOGLE_STACKDRIVER_MODULE", func() { it.Before(func() { - Expect(os.Setenv("BPL_GOOGLE_STACKDRIVER_MODULE", "test-module")).To(Succeed()) + Expect(os.Setenv("BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH", "test-path")).To(Succeed()) }) it.After(func() { - Expect(os.Unsetenv("BPL_GOOGLE_STACKDRIVER_MODULE")).To(Succeed()) + Expect(os.Unsetenv("BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH")).To(Succeed()) }) - it("uses configured module", func() { + 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=test-module", + "JAVA_TOOL_OPTIONS": "-agentpath:test-path=-logtostderr=1,-cprof_project_id=,-cprof_service=default-module", })) }) - }) - context("$BPL_GOOGLE_STACKDRIVER_VERSION", func() { - it.Before(func() { - Expect(os.Setenv("BPL_GOOGLE_STACKDRIVER_VERSION", "test-version")).To(Succeed()) + 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", + })) + }) }) - it.After(func() { - Expect(os.Unsetenv("BPL_GOOGLE_STACKDRIVER_VERSION")).To(Succeed()) + 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", + })) + }) }) - 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", + })) + }) }) }) + }) - context("$JAVA_TOOL_OPTIONS", func() { - it.Before(func() { - Expect(os.Setenv("JAVA_TOOL_OPTIONS", "test-java-tool-options")).To(Succeed()) - }) + context("with GCP", func() { + it.Before(func() { + j.InGCP = func() bool { return true } - it.After(func() { - Expect(os.Unsetenv("JAVA_TOOL_OPTIONS")).To(Succeed()) - }) + Expect(os.Setenv("BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH", "test-path")).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", - })) - }) + it.After(func() { + Expect(os.Unsetenv("BPI_GOOGLE_STACKDRIVER_PROFILER_JAVA_AGENT_PATH")).To(Succeed()) + }) + + it("configures environment", func() { + Expect(j.Execute()).NotTo(BeNil()) }) }) + } diff --git a/helper/nodejs_debugger.go b/helper/nodejs_debugger.go new file mode 100644 index 0000000..457b08e --- /dev/null +++ b/helper/nodejs_debugger.go @@ -0,0 +1,84 @@ +/* + * 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" + "io/ioutil" + "os" + "path/filepath" + "regexp" + + "github.com/buildpacks/libcnb" + + "github.com/paketo-buildpacks/libpak/bard" + "github.com/paketo-buildpacks/libpak/bindings" + "github.com/paketo-buildpacks/libpak/sherpa" +) + +type NodeJSDebugger struct { + ApplicationPath string + Bindings libcnb.Bindings + Logger bard.Logger + InGCP func() bool +} + +func (n NodeJSDebugger) Execute() (map[string]string, error) { + if !n.configured() { + return nil, nil + } + + n.Logger.Info("Google Stackdriver Debugger enabled") + + nodePath, err := sherpa.GetEnvRequired("BPI_GOOGLE_STACKDRIVER_DEBUGGER_NODEJS_NODE_PATH") + if err != nil { + return nil, err + } + + m, 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, m) + c, err := ioutil.ReadFile(file) + if err != nil { + return nil, 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 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), nodePath), + }, nil +} + +func (n NodeJSDebugger) configured() bool { + if _, ok, err := bindings.ResolveOne(n.Bindings, bindings.OfType("StackdriverDebugger")); err != nil { + return false + } else if ok { + return true + } + + return n.InGCP() +} diff --git a/helper/nodejs_debugger_test.go b/helper/nodejs_debugger_test.go new file mode 100644 index 0000000..80735d3 --- /dev/null +++ b/helper/nodejs_debugger_test.go @@ -0,0 +1,150 @@ +/* + * 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 ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/buildpacks/libcnb" + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + + "github.com/paketo-buildpacks/google-stackdriver/helper" +) + +func testNodeJSDebugger(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + + n helper.NodeJSDebugger + ) + + it.Before(func() { + var err error + n.ApplicationPath, err = ioutil.TempDir("", "node-debugger-application") + Expect(err).NotTo(HaveOccurred()) + + n.InGCP = func() bool { return false } + }) + + it.After(func() { + Expect(os.RemoveAll(n.ApplicationPath)).To(Succeed()) + }) + + it("does nothing without binding or GCP", func() { + Expect(n.Execute()).To(BeNil()) + }) + + context("with binding", func() { + it.Before(func() { + n.Bindings = libcnb.Bindings{libcnb.Binding{Type: "StackdriverDebugger"}} + }) + + it("fails without $BPI_GOOGLE_STACKDRIVER_DEBUGGER_NODEJS_NODE_PATH", func() { + _, err := n.Execute() + Expect(err).To(MatchError("$BPI_GOOGLE_STACKDRIVER_DEBUGGER_NODEJS_NODE_PATH must be set")) + }) + + context("$BPI_GOOGLE_STACKDRIVER_DEBUGGER_NODEJS_NODE_PATH", func() { + + it.Before(func() { + Expect(os.Setenv("BPI_GOOGLE_STACKDRIVER_DEBUGGER_NODEJS_NODE_PATH", "test-node-path-2")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("BPI_GOOGLE_STACKDRIVER_DEBUGGER_NODEJS_NODE_PATH")).To(Succeed()) + }) + + it("adds NODE_PATH", func() { + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), + 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "main.js"), []byte("test"), 0644)).To(Succeed()) + + Expect(n.Execute()).To(Equal(map[string]string{ + "NODE_PATH": "test-node-path-2", + })) + }) + + context("$JAVA_TOOL_OPTIONS", func() { + it.Before(func() { + Expect(os.Setenv("NODE_PATH", "test-node-path-1")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("NODE_PATH")).To(Succeed()) + }) + + it("uses configured NODE_PATH", func() { + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), + 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "main.js"), []byte("test"), 0644)).To(Succeed()) + + Expect(n.Execute()).To(Equal(map[string]string{ + "NODE_PATH": "test-node-path-1:test-node-path-2", + })) + }) + }) + + it("requires @google-cloud/debug-agent module", func() { + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), + 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "main.js"), []byte("test"), 0644)).To(Succeed()) + + _, err := n.Execute() + Expect(err).NotTo(HaveOccurred()) + + Expect(ioutil.ReadFile(filepath.Join(n.ApplicationPath, "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(n.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), + 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "main.js"), + []byte("test\nrequire('@google-cloud/debug-agent')\ntest"), 0644)).To(Succeed()) + + _, err := n.Execute() + Expect(err).NotTo(HaveOccurred()) + + Expect(ioutil.ReadFile(filepath.Join(n.ApplicationPath, "main.js"))).To(Equal( + []byte("test\nrequire('@google-cloud/debug-agent')\ntest"))) + }) + }) + }) + + context("with GCP", func() { + it.Before(func() { + n.InGCP = func() bool { return true } + Expect(os.Setenv("BPI_GOOGLE_STACKDRIVER_DEBUGGER_NODEJS_NODE_PATH", "test-path")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("BPI_GOOGLE_STACKDRIVER_DEBUGGER_NODEJS_NODE_PATH")).To(Succeed()) + }) + + it("configures environment", func() { + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), + 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "main.js"), []byte("test"), 0644)).To(Succeed()) + + Expect(n.Execute()).NotTo(BeNil()) + }) + }) +} diff --git a/helper/nodejs_profiler.go b/helper/nodejs_profiler.go new file mode 100644 index 0000000..3e4bbbf --- /dev/null +++ b/helper/nodejs_profiler.go @@ -0,0 +1,84 @@ +/* + * 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" + "io/ioutil" + "os" + "path/filepath" + "regexp" + + "github.com/buildpacks/libcnb" + + "github.com/paketo-buildpacks/libpak/bard" + "github.com/paketo-buildpacks/libpak/bindings" + "github.com/paketo-buildpacks/libpak/sherpa" +) + +type NodeJSProfiler struct { + ApplicationPath string + Bindings libcnb.Bindings + Logger bard.Logger + InGCP func() bool +} + +func (n NodeJSProfiler) Execute() (map[string]string, error) { + if !n.configured() { + return nil, nil + } + + n.Logger.Info("Google Stackdriver Profiler enabled") + + nodePath, err := sherpa.GetEnvRequired("BPI_GOOGLE_STACKDRIVER_PROFILER_NODEJS_NODE_PATH") + if err != nil { + return nil, err + } + + m, 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, m) + c, err := ioutil.ReadFile(file) + if err != nil { + return nil, 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 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), nodePath), + }, nil +} + +func (n NodeJSProfiler) configured() bool { + if _, ok, err := bindings.ResolveOne(n.Bindings, bindings.OfType("StackdriverProfiler")); err != nil { + return false + } else if ok { + return true + } + + return n.InGCP() +} diff --git a/helper/nodejs_profiler_test.go b/helper/nodejs_profiler_test.go new file mode 100644 index 0000000..45b8b6d --- /dev/null +++ b/helper/nodejs_profiler_test.go @@ -0,0 +1,150 @@ +/* + * 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 ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/buildpacks/libcnb" + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + + "github.com/paketo-buildpacks/google-stackdriver/helper" +) + +func testNodeJSProfiler(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + + n helper.NodeJSProfiler + ) + + it.Before(func() { + var err error + n.ApplicationPath, err = ioutil.TempDir("", "node-profiler-application") + Expect(err).NotTo(HaveOccurred()) + + n.InGCP = func() bool { return false } + }) + + it.After(func() { + Expect(os.RemoveAll(n.ApplicationPath)).To(Succeed()) + }) + + it("does nothing without binding or GCP", func() { + Expect(n.Execute()).To(BeNil()) + }) + + context("with binding", func() { + it.Before(func() { + n.Bindings = libcnb.Bindings{libcnb.Binding{Type: "StackdriverProfiler"}} + }) + + it("fails without $BPI_GOOGLE_STACKDRIVER_PROFILER_NODEJS_NODE_PATH", func() { + _, err := n.Execute() + Expect(err).To(MatchError("$BPI_GOOGLE_STACKDRIVER_PROFILER_NODEJS_NODE_PATH must be set")) + }) + + context("$BPI_GOOGLE_STACKDRIVER_PROFILER_NODEJS_NODE_PATH", func() { + + it.Before(func() { + Expect(os.Setenv("BPI_GOOGLE_STACKDRIVER_PROFILER_NODEJS_NODE_PATH", "test-node-path-2")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("BPI_GOOGLE_STACKDRIVER_PROFILER_NODEJS_NODE_PATH")).To(Succeed()) + }) + + it("adds NODE_PATH", func() { + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), + 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "main.js"), []byte("test"), 0644)).To(Succeed()) + + Expect(n.Execute()).To(Equal(map[string]string{ + "NODE_PATH": "test-node-path-2", + })) + }) + + context("$JAVA_TOOL_OPTIONS", func() { + it.Before(func() { + Expect(os.Setenv("NODE_PATH", "test-node-path-1")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("NODE_PATH")).To(Succeed()) + }) + + it("uses configured NODE_PATH", func() { + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), + 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "main.js"), []byte("test"), 0644)).To(Succeed()) + + Expect(n.Execute()).To(Equal(map[string]string{ + "NODE_PATH": "test-node-path-1:test-node-path-2", + })) + }) + }) + + it("requires @google-cloud/profiler module", func() { + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), + 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "main.js"), []byte("test"), 0644)).To(Succeed()) + + _, err := n.Execute() + Expect(err).NotTo(HaveOccurred()) + + Expect(ioutil.ReadFile(filepath.Join(n.ApplicationPath, "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(n.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), + 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "main.js"), + []byte("test\nrequire('@google-cloud/profiler')\ntest"), 0644)).To(Succeed()) + + _, err := n.Execute() + Expect(err).NotTo(HaveOccurred()) + + Expect(ioutil.ReadFile(filepath.Join(n.ApplicationPath, "main.js"))).To(Equal( + []byte("test\nrequire('@google-cloud/profiler')\ntest"))) + }) + }) + }) + + context("with GCP", func() { + it.Before(func() { + n.InGCP = func() bool { return true } + Expect(os.Setenv("BPI_GOOGLE_STACKDRIVER_PROFILER_NODEJS_NODE_PATH", "test-path")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("BPI_GOOGLE_STACKDRIVER_PROFILER_NODEJS_NODE_PATH")).To(Succeed()) + }) + + it("configures environment", func() { + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "package.json"), []byte(`{ "main": "main.js" }`), + 0644)).To(Succeed()) + Expect(ioutil.WriteFile(filepath.Join(n.ApplicationPath, "main.js"), []byte("test"), 0644)).To(Succeed()) + + Expect(n.Execute()).NotTo(BeNil()) + }) + }) +} diff --git a/stackdriver/build.go b/stackdriver/build.go index d8e449e..d46bdf7 100644 --- a/stackdriver/build.go +++ b/stackdriver/build.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/buildpacks/libcnb" + "github.com/paketo-buildpacks/libpak" "github.com/paketo-buildpacks/libpak/bard" ) @@ -75,9 +76,11 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) } - ja := NewNodeJSDebuggerAgent(context.Buildpack.Path, dep, dc, result.Plan) + ja := NewNodeJSDebuggerAgent(dep, dc, result.Plan) ja.Logger = b.Logger result.Layers = append(result.Layers, ja) + + names = append(names, "nodejs-debugger") } if _, ok, err := pr.Resolve("google-stackdriver-profiler-java"); err != nil { @@ -103,9 +106,11 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) } - ja := NewNodeJSProfilerAgent(context.Buildpack.Path, dep, dc, result.Plan) + ja := NewNodeJSProfilerAgent(dep, dc, result.Plan) ja.Logger = b.Logger result.Layers = append(result.Layers, ja) + + names = append(names, "nodejs-profiler") } h := libpak.NewHelperLayerContributor(context.Buildpack, result.Plan, names...) diff --git a/stackdriver/build_test.go b/stackdriver/build_test.go index c5d236c..0ba9386 100644 --- a/stackdriver/build_test.go +++ b/stackdriver/build_test.go @@ -21,9 +21,10 @@ import ( "github.com/buildpacks/libcnb" . "github.com/onsi/gomega" - "github.com/paketo-buildpacks/libpak" "github.com/sclevine/spec" + "github.com/paketo-buildpacks/libpak" + "github.com/paketo-buildpacks/google-stackdriver/stackdriver" ) @@ -75,7 +76,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(result.Layers).To(HaveLen(2)) Expect(result.Layers[0].Name()).To(Equal("google-stackdriver-debugger-nodejs")) 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{"credentials", "nodejs-debugger"})) }) it("contributes Java profiler agent", func() { @@ -119,6 +120,6 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(result.Layers).To(HaveLen(2)) Expect(result.Layers[0].Name()).To(Equal("google-stackdriver-profiler-nodejs")) 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{"credentials", "nodejs-profiler"})) }) } diff --git a/stackdriver/detect.go b/stackdriver/detect.go index 57ee547..55837b1 100644 --- a/stackdriver/detect.go +++ b/stackdriver/detect.go @@ -20,65 +20,149 @@ import ( "fmt" "github.com/buildpacks/libcnb" - "github.com/paketo-buildpacks/libpak/bindings" + "github.com/paketo-buildpacks/libpak" ) type Detect struct{} func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) { - result := libcnb.DetectResult{Pass: false} + 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.Resolve("BP_GOOGLE_STACKDRIVER_DEBUGGER_ENABLED") + _, profiler := cr.Resolve("BP_GOOGLE_STACKDRIVER_PROFILER_ENABLED") - 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"}, + if debugger && profiler { + return libcnb.DetectResult{ + Pass: true, + Plans: []libcnb.BuildPlan{ + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "google-stackdriver-debugger-nodejs"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "jvm-application"}, + {Name: "google-stackdriver-debugger-nodejs"}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-debugger-java"}, - {Name: "jvm-application"}, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "google-stackdriver-profiler-nodejs"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "jvm-application"}, + {Name: "google-stackdriver-profiler-nodejs"}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, }, - }, - libcnb.BuildPlan{ - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-debugger-nodejs"}, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-profiler-java"}, + {Name: "google-stackdriver-debugger-nodejs"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-profiler-java"}, + {Name: "jvm-application"}, + {Name: "google-stackdriver-debugger-nodejs"}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-profiler-java"}, + {Name: "google-stackdriver-profiler-nodejs"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-profiler-java"}, + {Name: "jvm-application"}, + {Name: "google-stackdriver-profiler-nodejs"}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-debugger-nodejs"}, - {Name: "node", Metadata: map[string]interface{}{"build": true}}, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "google-stackdriver-profiler-java"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "google-stackdriver-profiler-java"}, + {Name: "jvm-application"}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-debugger-nodejs"}, + {Name: "google-stackdriver-profiler-nodejs"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-debugger-nodejs"}, + {Name: "google-stackdriver-profiler-nodejs"}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, }, }, - ) + }, nil } - 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"}, + if debugger { + return 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"}, + }, }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-profiler-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}}, + }, }, }, - libcnb.BuildPlan{ - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-profiler-nodejs"}, + }, nil + } + + if profiler { + return 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"}, + }, }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-profiler-nodejs"}, - {Name: "node", Metadata: map[string]interface{}{"build": true}}, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-profiler-nodejs"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-profiler-nodejs"}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, }, }, - ) + }, nil } - return result, nil + return libcnb.DetectResult{Pass: false}, nil } diff --git a/stackdriver/detect_test.go b/stackdriver/detect_test.go index d599e8e..62b4655 100644 --- a/stackdriver/detect_test.go +++ b/stackdriver/detect_test.go @@ -17,6 +17,7 @@ package stackdriver_test import ( + "os" "testing" "github.com/buildpacks/libcnb" @@ -34,67 +35,171 @@ func testDetect(t *testing.T, context spec.G, it spec.S) { detect stackdriver.Detect ) - it("fails without service", func() { + it("fails without any environment variables", 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"}, + context("$BP_GOOGLE_STACKDRIVER_DEBUGGER_ENABLED and $BP_GOOGLE_STACKDRIVER_PROFILER_ENABLED", func() { + it.Before(func() { + Expect(os.Setenv("BP_GOOGLE_STACKDRIVER_DEBUGGER_ENABLED", "true")).To(Succeed()) + Expect(os.Setenv("BP_GOOGLE_STACKDRIVER_PROFILER_ENABLED", "true")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("BP_GOOGLE_STACKDRIVER_DEBUGGER_ENABLED")).To(Succeed()) + Expect(os.Unsetenv("BP_GOOGLE_STACKDRIVER_PROFILER_ENABLED")).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: "google-stackdriver-debugger-java"}, + {Name: "google-stackdriver-debugger-nodejs"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "jvm-application"}, + {Name: "google-stackdriver-debugger-nodejs"}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-debugger-java"}, - {Name: "jvm-application"}, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "google-stackdriver-profiler-nodejs"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "jvm-application"}, + {Name: "google-stackdriver-profiler-nodejs"}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, }, - }, - { - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-debugger-nodejs"}, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-profiler-java"}, + {Name: "google-stackdriver-debugger-nodejs"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-profiler-java"}, + {Name: "jvm-application"}, + {Name: "google-stackdriver-debugger-nodejs"}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-profiler-java"}, + {Name: "google-stackdriver-profiler-nodejs"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-profiler-java"}, + {Name: "jvm-application"}, + {Name: "google-stackdriver-profiler-nodejs"}, + {Name: "node", Metadata: map[string]interface{}{"build": true}}, + }, }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-debugger-nodejs"}, - {Name: "node", Metadata: map[string]interface{}{"build": true}}, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "google-stackdriver-profiler-java"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "google-stackdriver-profiler-java"}, + {Name: "jvm-application"}, + }, + }, + { + Provides: []libcnb.BuildPlanProvide{ + {Name: "google-stackdriver-debugger-nodejs"}, + {Name: "google-stackdriver-profiler-nodejs"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-debugger-nodejs"}, + {Name: "google-stackdriver-profiler-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"}, + context("$BP_GOOGLE_STACKDRIVER_DEBUGGER_ENABLED", func() { + it.Before(func() { + Expect(os.Setenv("BP_GOOGLE_STACKDRIVER_DEBUGGER_ENABLED", "true")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("BP_GOOGLE_STACKDRIVER_DEBUGGER_ENABLED")).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: "google-stackdriver-debugger-java"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-debugger-java"}, + {Name: "jvm-application"}, + }, }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-profiler-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}}, + }, }, }, - { - Provides: []libcnb.BuildPlanProvide{ - {Name: "google-stackdriver-profiler-nodejs"}, + })) + }) + + }) + + context("$BP_GOOGLE_STACKDRIVER_PROFILER_ENABLED", func() { + it.Before(func() { + Expect(os.Setenv("BP_GOOGLE_STACKDRIVER_PROFILER_ENABLED", "true")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("BP_GOOGLE_STACKDRIVER_PROFILER_ENABLED")).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: "google-stackdriver-profiler-java"}, + }, + Requires: []libcnb.BuildPlanRequire{ + {Name: "google-stackdriver-profiler-java"}, + {Name: "jvm-application"}, + }, }, - Requires: []libcnb.BuildPlanRequire{ - {Name: "google-stackdriver-profiler-nodejs"}, - {Name: "node", Metadata: map[string]interface{}{"build": true}}, + { + 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 index 3800852..cf95a19 100644 --- a/stackdriver/java_debugger_agent.go +++ b/stackdriver/java_debugger_agent.go @@ -46,8 +46,8 @@ func (j JavaDebuggerAgent) Contribute(layer libcnb.Layer) (libcnb.Layer, error) 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")) + layer.LaunchEnvironment.Default("BPI_GOOGLE_STACKDRIVER_DEBUGGER_JAVA_AGENT_PATH", + filepath.Join(layer.Path, "cdbg_java_agent.so")) return layer, nil }, libpak.LaunchLayer) diff --git a/stackdriver/java_debugger_agent_test.go b/stackdriver/java_debugger_agent_test.go index 84c199c..09ae169 100644 --- a/stackdriver/java_debugger_agent_test.go +++ b/stackdriver/java_debugger_agent_test.go @@ -17,7 +17,6 @@ package stackdriver_test import ( - "fmt" "io/ioutil" "os" "path/filepath" @@ -25,9 +24,10 @@ import ( "github.com/buildpacks/libcnb" . "github.com/onsi/gomega" - "github.com/paketo-buildpacks/libpak" "github.com/sclevine/spec" + "github.com/paketo-buildpacks/libpak" + "github.com/paketo-buildpacks/google-stackdriver/stackdriver" ) @@ -66,7 +66,6 @@ func testJavaDebuggerAgent(t *testing.T, context spec.G, it spec.S) { 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))) + Expect(layer.LaunchEnvironment["BPI_GOOGLE_STACKDRIVER_DEBUGGER_JAVA_AGENT_PATH.default"]).To(Equal(file)) }) } diff --git a/stackdriver/java_profiler_agent.go b/stackdriver/java_profiler_agent.go index b888bc6..65a0c6a 100644 --- a/stackdriver/java_profiler_agent.go +++ b/stackdriver/java_profiler_agent.go @@ -22,6 +22,7 @@ import ( "path/filepath" "github.com/buildpacks/libcnb" + "github.com/paketo-buildpacks/libpak" "github.com/paketo-buildpacks/libpak/bard" "github.com/paketo-buildpacks/libpak/crush" diff --git a/stackdriver/nodejs_debugger_agent.go b/stackdriver/nodejs_debugger_agent.go index 9e30d09..f1267a1 100644 --- a/stackdriver/nodejs_debugger_agent.go +++ b/stackdriver/nodejs_debugger_agent.go @@ -18,28 +18,24 @@ 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 { +func NewNodeJSDebuggerAgent(dependency libpak.BuildpackDependency, cache libpak.DependencyCache, plan *libcnb.BuildpackPlan) NodeJSDebuggerAgent { return NodeJSDebuggerAgent{ - ApplicationPath: applicationPath, Executor: effect.NewExecutor(), LayerContributor: libpak.NewDependencyLayerContributor(dependency, cache, plan), } @@ -48,7 +44,7 @@ func NewNodeJSDebuggerAgent(applicationPath string, dependency libpak.BuildpackD 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) { + 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{ @@ -61,34 +57,11 @@ func (n NodeJSDebuggerAgent) Contribute(layer libcnb.Layer) (libcnb.Layer, error 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")) + layer.LaunchEnvironment.Default("BPI_GOOGLE_STACKDRIVER_DEBUGGER_NODEJS_NODE_PATH", + 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 { diff --git a/stackdriver/nodejs_debugger_agent_test.go b/stackdriver/nodejs_debugger_agent_test.go index 2a1f6d1..a25e61e 100644 --- a/stackdriver/nodejs_debugger_agent_test.go +++ b/stackdriver/nodejs_debugger_agent_test.go @@ -24,11 +24,12 @@ import ( "github.com/buildpacks/libcnb" . "github.com/onsi/gomega" + "github.com/sclevine/spec" + "github.com/stretchr/testify/mock" + "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" ) @@ -44,9 +45,6 @@ func testNodeJSDebuggerAgent(t *testing.T, context spec.G, it spec.S) { 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()) @@ -55,22 +53,17 @@ func testNodeJSDebuggerAgent(t *testing.T, context spec.G, it spec.S) { }) 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 := stackdriver.NewNodeJSDebuggerAgent(dep, dc, &libcnb.BuildpackPlan{}) n.Executor = executor layer, err := ctx.Layers.Layer("test-layer") Expect(err).NotTo(HaveOccurred()) @@ -88,54 +81,8 @@ func testNodeJSDebuggerAgent(t *testing.T, context spec.G, it spec.S) { "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"))) + Expect(layer.LaunchEnvironment["BPI_GOOGLE_STACKDRIVER_DEBUGGER_NODEJS_NODE_PATH.default"]). + 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 index a156251..7910073 100644 --- a/stackdriver/nodejs_profiler_agent.go +++ b/stackdriver/nodejs_profiler_agent.go @@ -18,28 +18,24 @@ 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 { +func NewNodeJSProfilerAgent(dependency libpak.BuildpackDependency, cache libpak.DependencyCache, plan *libcnb.BuildpackPlan) NodeJSProfilerAgent { return NodeJSProfilerAgent{ - ApplicationPath: applicationPath, Executor: effect.NewExecutor(), LayerContributor: libpak.NewDependencyLayerContributor(dependency, cache, plan), } @@ -48,7 +44,7 @@ func NewNodeJSProfilerAgent(applicationPath string, dependency libpak.BuildpackD 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) { + 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{ @@ -61,34 +57,11 @@ func (n NodeJSProfilerAgent) Contribute(layer libcnb.Layer) (libcnb.Layer, error 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")) + layer.LaunchEnvironment.Default("BPI_GOOGLE_STACKDRIVER_PROFILER_NODEJS_NODE_PATH", + 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 { diff --git a/stackdriver/nodejs_profiler_agent_test.go b/stackdriver/nodejs_profiler_agent_test.go index 9fc7638..6186daf 100644 --- a/stackdriver/nodejs_profiler_agent_test.go +++ b/stackdriver/nodejs_profiler_agent_test.go @@ -24,11 +24,12 @@ import ( "github.com/buildpacks/libcnb" . "github.com/onsi/gomega" + "github.com/sclevine/spec" + "github.com/stretchr/testify/mock" + "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" ) @@ -44,9 +45,6 @@ func testNodeJSProfilerAgent(t *testing.T, context spec.G, it spec.S) { 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()) @@ -55,22 +53,17 @@ func testNodeJSProfilerAgent(t *testing.T, context spec.G, it spec.S) { }) 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 := stackdriver.NewNodeJSProfilerAgent(dep, dc, &libcnb.BuildpackPlan{}) n.Executor = executor layer, err := ctx.Layers.Layer("test-layer") Expect(err).NotTo(HaveOccurred()) @@ -88,54 +81,7 @@ func testNodeJSProfilerAgent(t *testing.T, context spec.G, it spec.S) { "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"))) + Expect(layer.LaunchEnvironment["BPI_GOOGLE_STACKDRIVER_PROFILER_NODEJS_NODE_PATH.default"]). + To(Equal(filepath.Join(layer.Path, "node_modules"))) }) }