Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
unreleased
=====================

* Add Dynatrace OneAgent injection support (via github.com/Dynatrace/libbuildpack-dynatrace). When a Dynatrace service is bound, the OneAgent is downloaded during staging and LD_PRELOADed into the app at launch. Defaults to the generic `process` code module; use the `addtechnologies` binding field to add language modules (e.g. `go`).


v2.0.0 Jun 15, 2026
=====================

Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,38 @@ To test this buildpack, run the following command from the buildpack's directory
./scripts/integration.sh
```

### Dynatrace Integration

This buildpack can automatically inject the [Dynatrace OneAgent](https://www.dynatrace.com/support/help/technology-support/cloud-platforms/cloud-foundry/) into your application. When a Dynatrace service is bound to the app, the buildpack downloads the OneAgent during staging and configures `LD_PRELOAD` (via `profile.d/dynatrace-env.sh`) so the agent is loaded into your binary at launch.

Bind a Dynatrace user-provided service (the service name must contain `dynatrace`):

```bash
cf create-user-provided-service dynatrace -p '{"environmentid":"<env-id>","apitoken":"<paas-token>"}'
cf bind-service my_app dynatrace
cf restage my_app
```

Supported credential fields:

| Key | Type | Description | Required | Default |
| --------------- | ------- | ------------------------------------------------------------------------------------------------------- | -------- | --------------- |
| environmentid | string | The ID for the Dynatrace environment. | Yes | N/A |
| apitoken | string | The API Token for the Dynatrace environment. | Yes | N/A |
| apiurl | string | Overrides the default Dynatrace API URL to connect to. | No | Default API URL |
| skiperrors | boolean | If `true`, staging does not fail when the OneAgent download fails. | No | false |
| networkzone | string | If set, the agent is configured to use communication endpoints located in this network zone. | No | empty |
| enablefips | boolean | If `true`, FIPS 140-2 mode is enabled. | No | false |
| addtechnologies | string | Comma-separated list of additional OneAgent code modules to download (e.g. `go`, `java`, `nodejs`). | No | empty |

By default the buildpack downloads the generic `process` code module. Since the
binary buildpack runs an opaque binary, it cannot detect the application's
language. For language-specific code-level insights, set `addtechnologies`
accordingly — e.g. `"addtechnologies":"go"` for a Go binary. Note that OneAgent
injection relies on `LD_PRELOAD`, so the binary must be **dynamically linked**
(for Go, built with `CGO_ENABLED=1`); a fully statically-linked binary ignores
`LD_PRELOAD` and will not be instrumented.

### Contributing

Find our guidelines [here](./CONTRIBUTING.md).
Expand Down
3 changes: 3 additions & 0 deletions fixtures/util/dynatrace/dynatrace-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

export DT_HELLO=some-value
11 changes: 11 additions & 0 deletions fixtures/util/dynatrace/fake_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"revision":1234567890,
"properties":
[
{
"section":"fakesection",
"key":"fakekey",
"value":"fakevalue"
}
]
}
3 changes: 3 additions & 0 deletions fixtures/util/dynatrace/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module myapp

go 1.26
19 changes: 19 additions & 0 deletions fixtures/util/dynatrace/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash

function main() {
set -e

local dir
dir="${1}"
echo "dir -> ${dir}"

mkdir -p "${dir}/dynatrace/oneagent/agent/lib64"
mkdir -p "${dir}/dynatrace/oneagent/agent/conf"

curl -s --fail "http://{{.URI}}/manifest.json" > "${dir}/dynatrace/oneagent/manifest.json"
curl -s --fail "http://{{.URI}}/dynatrace-env.sh" > "${dir}/dynatrace/oneagent/dynatrace-env.sh"
curl -s --fail "http://{{.URI}}/liboneagentproc.so" > "${dir}/dynatrace/oneagent/agent/lib64/liboneagentproc.so"
curl -s --fail "http://{{.URI}}/ruxitagentproc.conf" > "${dir}/dynatrace/oneagent/agent/conf/ruxitagentproc.conf"
}

main "${@}"
Binary file added fixtures/util/dynatrace/liboneagentproc.so
Binary file not shown.
85 changes: 85 additions & 0 deletions fixtures/util/dynatrace/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"text/template"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
var withoutAgentPath bool
path := req.URL.Path

if strings.HasPrefix(path, "/without-agent-path") {
path = strings.TrimPrefix(path, "/without-agent-path")
withoutAgentPath = true
}

switch path {
case "/v1/deployment/installer/agent/unix/paas-sh/latest":
context := struct{ URI string }{URI: req.Host}
t := template.Must(template.New("install.sh").ParseFiles("install.sh"))
err := t.Execute(w, context)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, err.Error())
return
}

case "/dynatrace-env.sh", "/liboneagentproc.so", "/ruxitagentproc.conf":
contents, err := os.ReadFile(strings.TrimPrefix(req.URL.Path, "/"))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, err.Error())
return
}

fmt.Fprintf(w, "%s", contents)

case "/manifest.json":
var payload map[string]interface{}
file, err := os.Open("manifest.json")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}

err = json.NewDecoder(file).Decode(&payload)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}

if withoutAgentPath {
payload["technologies"] = map[string]interface{}{
"process": map[string]interface{}{
"linux-x86-64": []struct{}{},
},
}
}

json.NewEncoder(w).Encode(payload)

case "/v1/deployment/installer/agent/processmoduleconfig":
fakeConfig, err := os.ReadFile("fake_config.json")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.Write(fakeConfig)

default:
w.WriteHeader(http.StatusNotFound)
}
})

log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", os.Getenv("PORT")), nil))
}
12 changes: 12 additions & 0 deletions fixtures/util/dynatrace/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"technologies": {
"process": {
"linux-x86-64": [
{
"path": "agent/lib64/liboneagentproc.so",
"binarytype": "primary"
}
]
}
}
}
3 changes: 3 additions & 0 deletions fixtures/util/dynatrace/ruxitagentproc.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# some comment
[testsection]
testkey testvalue
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/cloudfoundry/binary-buildpack
go 1.24.12

require (
github.com/Dynatrace/libbuildpack-dynatrace v1.9.0
github.com/cloudfoundry/libbuildpack v0.0.0-20260306125332-dcaf55eb6f33
github.com/cloudfoundry/switchblade v0.9.5
github.com/onsi/ginkgo/v2 v2.28.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/CycloneDX/cyclonedx-go v0.7.1/go.mod h1:N/nrdWQI2SIjaACyyDs/u7+ddCkyl/zkNs8xFsHF2Ps=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Dynatrace/libbuildpack-dynatrace v1.9.0 h1:3tJzXt7VVTsvPPS9Q7+e/KAk0ccC2eAbgHib5C/XRhA=
github.com/Dynatrace/libbuildpack-dynatrace v1.9.0/go.mod h1:Uu9aa5UFAk1Ua+zZXnvzo+avDXuEi+GtegeOyja9xg4=
github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs=
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
Expand Down
2 changes: 2 additions & 0 deletions src/binary/finalize/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"os"
"time"

_ "github.com/cloudfoundry/binary-buildpack/src/binary/hooks"

"github.com/cloudfoundry/binary-buildpack/src/binary/finalize"

"github.com/cloudfoundry/libbuildpack"
Expand Down
10 changes: 10 additions & 0 deletions src/binary/hooks/dynatrace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package hooks

import (
"github.com/cloudfoundry/libbuildpack"
"github.com/Dynatrace/libbuildpack-dynatrace"
)

func init() {
libbuildpack.AddHook(dynatrace.NewHook("process"))
}
Loading
Loading