Skip to content

Commit

Permalink
Conditional at Launch
Browse files Browse the repository at this point in the history
Previously, a binding was required at build time in order to contribute the
layers for the Stackdriver components.  We've started to think that requiring
a binding during build is too onerous a restriction and instead environment
variables should be used to indicate that a dependency should be contributed.
The knock-on effect of this change is that we have to move a lot more of the
conditional logic out to launch time including ensuring that if the binding
doesn't exist _nothing_ is contributed.  This change makes those updates to
the buildpack's behavior.

In addition, this change also includes an update that enables the buildpack to
determine if the application is running in GCP by detecting the metadata
server.  In this case, no binding is required at launch time either as the
credentials typically provided by the binding are instead provided by the
metadata server.

Finally, this includes a fix where the provided build plan wasn't anywhere
near exhaustive for combinations that a user need when using the buildpack.

Signed-off-by: Ben Hale <[email protected]>
  • Loading branch information
nebhale committed Nov 13, 2020
1 parent 769c74b commit a68009c
Show file tree
Hide file tree
Showing 27 changed files with 1,069 additions and 418 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
10 changes: 10 additions & 0 deletions buildpack.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
26 changes: 14 additions & 12 deletions cmd/helper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"os"

"github.com/buildpacks/libcnb"

"github.com/paketo-buildpacks/libpak/bard"
"github.com/paketo-buildpacks/libpak/sherpa"

Expand All @@ -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},
})
})
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 4 additions & 14 deletions helper/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
package helper

import (
"fmt"

"github.com/buildpacks/libcnb"

"github.com/paketo-buildpacks/libpak/bard"
"github.com/paketo-buildpacks/libpak/bindings"
)
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion helper/credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
39 changes: 39 additions & 0 deletions helper/in_gcp.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 2 additions & 0 deletions helper/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
49 changes: 34 additions & 15 deletions helper/java_debugger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
115 changes: 80 additions & 35 deletions helper/java_debugger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"os"
"testing"

"github.com/buildpacks/libcnb"
. "github.com/onsi/gomega"
"github.com/sclevine/spec"

Expand All @@ -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())
})
})
}
Loading

0 comments on commit a68009c

Please sign in to comment.