diff --git a/fixtures/fake_dynatrace_api/Default.aspx b/fixtures/fake_dynatrace_api/Default.aspx
new file mode 100755
index 00000000..314b598e
--- /dev/null
+++ b/fixtures/fake_dynatrace_api/Default.aspx
@@ -0,0 +1 @@
+<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
diff --git a/fixtures/fake_dynatrace_api/Default.aspx.cs b/fixtures/fake_dynatrace_api/Default.aspx.cs
new file mode 100755
index 00000000..5f943fe1
--- /dev/null
+++ b/fixtures/fake_dynatrace_api/Default.aspx.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections;
+using System.Linq;
+using System.Web;
+using System.Web.UI;
+using System.Web.UI.WebControls;
+using System.Collections.Generic;
+using System.IO;
+
+public partial class _Default : System.Web.UI.Page
+{
+ protected void Page_Load(object sender, EventArgs e)
+ {
+ string path = HttpContext.Current.Request.Headers["X-Original-URL"];
+ switch(path) {
+ case "/v1/deployment/installer/agent/windows/paas/latest?bitness=64&include=dotnet&include=process":
+ case "/v1/deployment/installer/agent/windows/paas/latest?bitness=64&include=dotnet&include=process&networkZone=testzone":
+ string zipFilePath = Server.MapPath("paas.zip");
+ Response.ContentType = "application/zip";
+ Response.AddHeader("Content-Disposition", "attachment; filename=paas.zip");
+ Response.WriteFile(zipFilePath);
+ break;
+ case "/no-manifest":
+ string zipFilePathNoManifest = Server.MapPath("paas-no-manifest.zip");
+ Response.ContentType = "application/zip";
+ Response.AddHeader("Content-Disposition", "attachment; filename=paas.zip");
+ Response.WriteFile(zipFilePathNoManifest);
+ break;
+ case "/v1/deployment/installer/agent/processmoduleconfig":
+ string configPath = Server.MapPath("fake_config.json");
+ Response.ContentType = "application/json";
+ Response.WriteFile(configPath);
+ break;
+ default:
+ Response.StatusCode = 404;
+ break;
+ }
+
+ Response.End();
+ }
+}
diff --git a/fixtures/fake_dynatrace_api/FakeAPI.csproj b/fixtures/fake_dynatrace_api/FakeAPI.csproj
new file mode 100644
index 00000000..609b39ce
--- /dev/null
+++ b/fixtures/fake_dynatrace_api/FakeAPI.csproj
@@ -0,0 +1,136 @@
+
+
+
+
+ Debug
+ AnyCPU
+
+
+
+
+ {B29B307C-710A-4ABA-820C-89F943105CE2}
+ {349c5851-65df-11da-9384-00065b846f21};{F184B08F-C81C-45F6-A57F-5ABD9991F28F}
+ Library
+ FakeAPI
+ FakeAPI
+ v4.5
+ Custom
+ true
+
+
+
+
+
+
+
+ true
+ full
+ true
+ true
+ bin\
+
+
+ 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022
+
+
+ pdbonly
+ false
+ true
+ true
+ bin\
+
+
+ 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ASPXCodeBehind
+
+
+ Designer
+
+
+
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+ On
+
+
+ Binary
+
+
+ Off
+
+
+ On
+
+
+
+
+
+
+
+
+ True
+ True
+ 5941
+ /
+ http://localhost:5941/
+ False
+ False
+
+
+ False
+
+
+
+
+
diff --git a/fixtures/fake_dynatrace_api/Web.config b/fixtures/fake_dynatrace_api/Web.config
new file mode 100755
index 00000000..9814a716
--- /dev/null
+++ b/fixtures/fake_dynatrace_api/Web.config
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fixtures/fake_dynatrace_api/fake_config.json b/fixtures/fake_dynatrace_api/fake_config.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/fixtures/fake_dynatrace_api/fake_config.json
@@ -0,0 +1 @@
+{}
diff --git a/fixtures/fake_dynatrace_api/paas-no-manifest.zip b/fixtures/fake_dynatrace_api/paas-no-manifest.zip
new file mode 100644
index 00000000..5958e611
Binary files /dev/null and b/fixtures/fake_dynatrace_api/paas-no-manifest.zip differ
diff --git a/fixtures/fake_dynatrace_api/paas.zip b/fixtures/fake_dynatrace_api/paas.zip
new file mode 100644
index 00000000..8f8ffe63
Binary files /dev/null and b/fixtures/fake_dynatrace_api/paas.zip differ
diff --git a/go.mod b/go.mod
index c417257c..7f162cee 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ require (
github.com/golang/mock v1.6.0
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.30.0
+ github.com/Dynatrace/libbuildpack-dynatrace v1.7.0
)
require (
diff --git a/go.sum b/go.sum
index 46e03ff6..a2679142 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,8 @@
code.cloudfoundry.org/lager v2.0.0+incompatible h1:WZwDKDB2PLd/oL+USK4b4aEjUymIej9My2nUQ9oWEwQ=
code.cloudfoundry.org/lager v2.0.0+incompatible/go.mod h1:O2sS7gKP3HM2iemG+EnwvyNQK7pTSC6Foi4QiMp9sSk=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/Dynatrace/libbuildpack-dynatrace v1.7.0 h1:t1k1qjzZs0dV9GrA8WjfJNueeJvhp/jnHg3GQuvqL8o=
+github.com/Dynatrace/libbuildpack-dynatrace v1.7.0/go.mod h1:Uu9aa5UFAk1Ua+zZXnvzo+avDXuEi+GtegeOyja9xg4=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
diff --git a/src/hwc/hooks/dynatrace.go b/src/hwc/hooks/dynatrace.go
new file mode 100644
index 00000000..cf35ba38
--- /dev/null
+++ b/src/hwc/hooks/dynatrace.go
@@ -0,0 +1,10 @@
+package hooks
+
+import (
+ dynatrace "github.com/Dynatrace/libbuildpack-dynatrace"
+ "github.com/cloudfoundry/libbuildpack"
+)
+
+func init() {
+ libbuildpack.AddHook(dynatrace.NewHook("dotnet", "process"))
+}
diff --git a/src/hwc/integration/deploy_an_app_with_dynatrace_test.go b/src/hwc/integration/deploy_an_app_with_dynatrace_test.go
new file mode 100644
index 00000000..21845ade
--- /dev/null
+++ b/src/hwc/integration/deploy_an_app_with_dynatrace_test.go
@@ -0,0 +1,266 @@
+package integration_test
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "time"
+
+ "github.com/cloudfoundry/libbuildpack/cutlass"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("CF HWC Buildpack", func() {
+ var (
+ app *cutlass.App
+ createdServices []string
+ dynatraceAPI *cutlass.App
+ dynatraceAPIURI string
+ )
+
+ BeforeEach(func() {
+ dynatraceAPI = cutlass.New(Fixtures("fake_dynatrace_api"))
+ dynatraceAPI.SetEnv("BP_DEBUG", "true")
+ dynatraceAPI.Stack = os.Getenv("CF_STACK")
+
+ Expect(dynatraceAPI.Push()).To(Succeed())
+ Eventually(func() ([]string, error) { return dynatraceAPI.InstanceStates() }, 60*time.Second).Should(Equal([]string{"RUNNING"}))
+
+ var err error
+ dynatraceAPIURI, err = dynatraceAPI.GetUrl("")
+ Expect(err).NotTo(HaveOccurred())
+
+ app = cutlass.New(Fixtures("windows_app"))
+ app.SetEnv("BP_DEBUG", "true")
+ app.Stack = os.Getenv("CF_STACK")
+ PushAppAndConfirm(app)
+
+ createdServices = make([]string, 0)
+ })
+
+ AfterEach(func() {
+ if app != nil {
+ app.Destroy()
+ app = nil
+ }
+
+ if dynatraceAPI != nil {
+ dynatraceAPI.Destroy()
+ }
+ dynatraceAPI = nil
+
+ for _, service := range createdServices {
+ command := exec.Command("cf", "delete-service", "-f", service)
+ _, err := command.Output()
+ Expect(err).To(BeNil())
+ }
+ })
+
+ Context("deploying a HWC app with Dynatrace agent with single credentials service", func() {
+ It("checks if Dynatrace injection was successful", func() {
+ serviceName := "dynatrace-" + cutlass.RandStringRunes(20) + "-service"
+ command := exec.Command("cf", "cups", serviceName, "-p", fmt.Sprintf("'{\"apitoken\":\"secretpaastoken\",\"apiurl\":\"%s\",\"environmentid\":\"envid\"}'", dynatraceAPIURI))
+ _, err := command.CombinedOutput()
+ Expect(err).To(BeNil())
+ createdServices = append(createdServices, serviceName)
+
+ command = exec.Command("cf", "bind-service", app.Name, serviceName)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+ command = exec.Command("cf", "restage", app.Name)
+ _, err = command.Output()
+ Expect(err).To(BeNil())
+
+ Expect(app.ConfirmBuildpack(buildpackVersion)).To(Succeed())
+ Expect(app.Stdout.String()).To(Not(ContainSubstring("manifest.json not found, using fallback!")))
+ Expect(app.Stdout.String()).To(ContainSubstring("Dynatrace service credentials found. Setting up Dynatrace OneAgent."))
+ Expect(app.Stdout.String()).To(ContainSubstring("Dynatrace OneAgent injection is set up."))
+ })
+ })
+
+ Context("deploying a HWC app with Dynatrace agent with configured network zone", func() {
+ It("checks if Dynatrace injection was successful", func() {
+ serviceName := "dynatrace-" + cutlass.RandStringRunes(20) + "-service"
+ command := exec.Command("cf", "cups", serviceName, "-p", fmt.Sprintf("'{\"apitoken\":\"secretpaastoken\",\"apiurl\":\"%s\",\"environmentid\":\"envid\", \"networkzone\":\"testzone\"}'", dynatraceAPIURI))
+ _, err := command.CombinedOutput()
+ Expect(err).To(BeNil())
+ createdServices = append(createdServices, serviceName)
+
+ command = exec.Command("cf", "bind-service", app.Name, serviceName)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+ command = exec.Command("cf", "restage", app.Name)
+ _, err = command.Output()
+ Expect(err).To(BeNil())
+
+ Expect(app.ConfirmBuildpack(buildpackVersion)).To(Succeed())
+ Expect(app.Stdout.String()).To(ContainSubstring("Setting DT_NETWORK_ZONE..."))
+ Expect(app.Stdout.String()).To(ContainSubstring("Dynatrace service credentials found. Setting up Dynatrace OneAgent."))
+ Expect(app.Stdout.String()).To(ContainSubstring("Dynatrace OneAgent injection is set up."))
+ })
+ })
+
+ Context("deploying a HWC app with Dynatrace agent with two credentials services", func() {
+ It("checks if detection of second service with credentials works", func() {
+ credentialsServiceName := "dynatrace-" + cutlass.RandStringRunes(20) + "-service"
+ command := exec.Command("cf", "cups", credentialsServiceName, "-p", fmt.Sprintf("'{\"apitoken\":\"secretpaastoken\",\"apiurl\":\"%s\",\"environmentid\":\"envid\"}'", dynatraceAPIURI))
+ _, err := command.CombinedOutput()
+ Expect(err).To(BeNil())
+ createdServices = append(createdServices, credentialsServiceName)
+
+ duplicateCredentialsServiceName := "dynatrace-dupe-" + cutlass.RandStringRunes(20) + "-service"
+ command = exec.Command("cf", "cups", duplicateCredentialsServiceName, "-p", fmt.Sprintf("'{\"apitoken\":\"secretpaastoken\",\"apiurl\":\"%s\",\"environmentid\":\"envid\"}'", dynatraceAPIURI))
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+ createdServices = append(createdServices, duplicateCredentialsServiceName)
+
+ command = exec.Command("cf", "bind-service", app.Name, credentialsServiceName)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+ command = exec.Command("cf", "bind-service", app.Name, duplicateCredentialsServiceName)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+
+ command = exec.Command("cf", "restage", app.Name)
+ _, err = command.Output()
+ Expect(err).To(BeNil())
+
+ Expect(app.Stdout.String()).To(ContainSubstring("More than one matching service found!"))
+ })
+ })
+
+ Context("deploying a HWC app with Dynatrace agent with failing agent download and ignoring errors", func() {
+ It("checks if skipping download errors works", func() {
+ credentialsServiceName := "dynatrace-" + cutlass.RandStringRunes(20) + "-service"
+ command := exec.Command("cf", "cups", credentialsServiceName, "-p", fmt.Sprintf("'{\"apitoken\":\"secretpaastoken\",\"apiurl\":\"%s/no-such-endpoint\",\"environmentid\":\"envid\",\"skiperrors\":\"true\"}'", dynatraceAPIURI))
+ _, err := command.CombinedOutput()
+ Expect(err).To(BeNil())
+ createdServices = append(createdServices, credentialsServiceName)
+
+ command = exec.Command("cf", "bind-service", app.Name, credentialsServiceName)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+
+ command = exec.Command("cf", "restage", app.Name)
+ _, err = command.Output()
+ Expect(err).To(BeNil())
+
+ Expect(app.Stdout.String()).To(ContainSubstring("Download returned with status 404"))
+ Expect(app.Stdout.String()).To(ContainSubstring("Error during installer download"))
+ })
+ })
+
+ Context("deploying a HWC app with Dynatrace agent with two dynatrace services", func() {
+ It("check if service detection isn't disturbed by a service with tags", func() {
+ credentialsServiceName := "dynatrace-" + cutlass.RandStringRunes(20) + "-service"
+ command := exec.Command("cf", "cups", credentialsServiceName, "-p", fmt.Sprintf("'{\"apitoken\":\"secretpaastoken\",\"apiurl\":\"%s\",\"environmentid\":\"envid\"}'", dynatraceAPIURI))
+ _, err := command.CombinedOutput()
+ Expect(err).To(BeNil())
+ createdServices = append(createdServices, credentialsServiceName)
+
+ tagsServiceName := "dynatrace-tags-" + cutlass.RandStringRunes(20) + "-service"
+ command = exec.Command("cf", "cups", tagsServiceName, "-p", "'{\"tag:dttest\":\"dynatrace_test\"}'")
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+ createdServices = append(createdServices, tagsServiceName)
+
+ command = exec.Command("cf", "bind-service", app.Name, credentialsServiceName)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+ command = exec.Command("cf", "bind-service", app.Name, tagsServiceName)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+
+ command = exec.Command("cf", "restage", app.Name)
+ _, err = command.Output()
+ Expect(err).To(BeNil())
+
+ Expect(app.ConfirmBuildpack(buildpackVersion)).To(Succeed())
+ Expect(app.Stdout.String()).To(ContainSubstring("Dynatrace service credentials found. Setting up Dynatrace OneAgent."))
+ Expect(app.Stdout.String()).To(ContainSubstring("Dynatrace OneAgent injection is set up."))
+ })
+ })
+
+ Context("deploying a HWC app with Dynatrace agent with single credentials service and without manifest.json", func() {
+ It("checks if Dynatrace injection was successful", func() {
+ serviceName := "dynatrace-" + cutlass.RandStringRunes(20) + "-service"
+ fmt.Println("Creating service...")
+ command := exec.Command("cf", "cups", serviceName, "-p", fmt.Sprintf("'{\"apitoken\":\"secretpaastoken\",\"apiurl\":\"%s\",\"environmentid\":\"envid\",\"customoneagenturl\":\"%s/no-manifest\"}'", dynatraceAPIURI, dynatraceAPIURI))
+ _, err := command.CombinedOutput()
+ Expect(err).To(BeNil())
+ createdServices = append(createdServices, serviceName)
+
+ fmt.Println("Binding service...")
+ command = exec.Command("cf", "bind-service", app.Name, serviceName)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+
+ fmt.Println("Restage app...")
+ command = exec.Command("cf", "restage", app.Name)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+
+ fmt.Println("Check output...")
+ Expect(app.ConfirmBuildpack(buildpackVersion)).To(Succeed())
+ Expect(app.Stdout.String()).To(ContainSubstring("manifest.json not found, using fallback!"))
+ Expect(app.Stdout.String()).To(ContainSubstring("Dynatrace service credentials found. Setting up Dynatrace OneAgent."))
+ Expect(app.Stdout.String()).To(ContainSubstring("Dynatrace OneAgent injection is set up."))
+ })
+ })
+
+ Context("deploying a HWC app with Dynatrace agent with failing agent download and checking retry", func() {
+ It("checks if retrying downloads works", func() {
+ credentialsServiceName := "dynatrace-" + cutlass.RandStringRunes(20) + "-service"
+ command := exec.Command("cf", "cups", credentialsServiceName, "-p", fmt.Sprintf("'{\"apitoken\":\"secretpaastoken\",\"apiurl\":\"%s/no-such-endpoint\",\"environmentid\":\"envid\"}'", dynatraceAPIURI))
+ _, err := command.CombinedOutput()
+ Expect(err).To(BeNil())
+ createdServices = append(createdServices, credentialsServiceName)
+
+ command = exec.Command("cf", "bind-service", app.Name, credentialsServiceName)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+
+ command = exec.Command("cf", "restage", app.Name)
+ _, err = command.CombinedOutput()
+ Expect(err).To(Not(BeNil()))
+
+ Eventually(app.Stdout.String).Should(ContainSubstring("Error during installer download, retrying in 4s"))
+ Eventually(app.Stdout.String).Should(ContainSubstring("Error during installer download, retrying in 5s"))
+ Eventually(app.Stdout.String).Should(ContainSubstring("Error during installer download, retrying in 7s"))
+ Eventually(app.Stdout.String).Should(ContainSubstring("Download returned with status 404"))
+ Eventually(app.Stdout.String).Should(ContainSubstring("Failed to compile droplet"))
+ })
+ })
+
+ Context("deploying a HWC app with Dynatrace agent with single credentials service and a redis service", func() {
+ It("checks if Dynatrace injection was successful", func() {
+ serviceName := "dynatrace-" + cutlass.RandStringRunes(20) + "-service"
+ command := exec.Command("cf", "cups", serviceName, "-p", fmt.Sprintf("'{\"apitoken\":\"secretpaastoken\",\"apiurl\":\"%s\",\"environmentid\":\"envid\"}'", dynatraceAPIURI))
+ _, err := command.CombinedOutput()
+ Expect(err).To(BeNil())
+ createdServices = append(createdServices, serviceName)
+ command = exec.Command("cf", "bind-service", app.Name, serviceName)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+
+ redisServiceName := "redis-" + cutlass.RandStringRunes(20) + "-service"
+ command = exec.Command("cf", "cups", redisServiceName, "-p", "'{\"name\":\"redis\", \"credentials\":{\"db_type\":\"redis\", \"instance_administration_api\":{\"deployment_id\":\"12345asdf\", \"instance_id\":\"12345asdf\", \"root\":\"https://doesnotexi.st\"}}}'")
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+ createdServices = append(createdServices, redisServiceName)
+ command = exec.Command("cf", "bind-service", app.Name, redisServiceName)
+ _, err = command.CombinedOutput()
+ Expect(err).To(BeNil())
+
+ command = exec.Command("cf", "restage", app.Name)
+ _, err = command.Output()
+ Expect(err).To(BeNil())
+
+ Expect(app.ConfirmBuildpack(buildpackVersion)).To(Succeed())
+ Expect(app.Stdout.String()).To(ContainSubstring("Dynatrace service credentials found. Setting up Dynatrace OneAgent."))
+ Expect(app.Stdout.String()).To(ContainSubstring("Dynatrace OneAgent injection is set up."))
+ })
+ })
+})
diff --git a/src/hwc/integration/integration_suite_test.go b/src/hwc/integration/integration_suite_test.go
index a9778656..dca7f3c0 100644
--- a/src/hwc/integration/integration_suite_test.go
+++ b/src/hwc/integration/integration_suite_test.go
@@ -190,3 +190,11 @@ func AssertNoInternetTraffic(fixtureName string) {
Expect(traffic).To(BeEmpty())
})
}
+
+func Fixtures(names ...string) string {
+ root, err := cutlass.FindRoot()
+ Expect(err).NotTo(HaveOccurred())
+
+ names = append([]string{root, "fixtures"}, names...)
+ return filepath.Join(names...)
+}
diff --git a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/.gitignore b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/.gitignore
new file mode 100644
index 00000000..f1c181ec
--- /dev/null
+++ b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/.gitignore
@@ -0,0 +1,12 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
diff --git a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/LICENSE b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/LICENSE
new file mode 100644
index 00000000..989e2c59
--- /dev/null
+++ b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/LICENSE
@@ -0,0 +1,201 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/README.md b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/README.md
new file mode 100644
index 00000000..23b3abd9
--- /dev/null
+++ b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/README.md
@@ -0,0 +1,71 @@
+# libbuildpack-dynatrace
+
+Base Library for Go-based Cloud Foundry Buildpack integrations with Dynatrace.
+
+## Summary
+
+The library provides the `Hook` struct that implements the `libbuildpack.Hook` interface that it's requested by the CF Buildpacks.
+
+On the buildpacks, you're expected to provide a hook through a `init()` function where you can register such hook implementation. For example, a simple implementation could be,
+
+```go
+import (
+ "github.com/cloudfoundry/libbuildpack"
+ "github.com/Dynatrace/libbuildpack-dynatrace"
+)
+
+func init() {
+ libbuildpack.AddHook(dynatrace.NewHook("nodejs", "process"))
+}
+```
+
+## Configuration
+
+The Hook will look for credentials in the configurations for existing services (which is represented in the runtime as the VCAP_SERVICES environment variable in JSON format.) We look for service names having the 'dynatrace' substring.
+
+We support the following configuration 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, the deployment doesn't fail if the Dynatrace agent download fails. | No | false |
+| networkzone | string | If set, agent is configured to choose communication endpoints located at the field's value. | No | empty |
+| enablefips | boolean | If true, the [FIPS 140-2 mode](https://www.dynatrace.com/news/blog/dynatrace-achieves-fips-140-2-certification/) is enabled | No | false |
+
+For example,
+
+```bash
+cf create-user-provided-service dynatrace -p '{"environmentid":"...","apitoken":"..."}'
+```
+
+See more at the documentation for [`cf create-user-provided-service`](http://cli.cloudfoundry.org/en-US/cf/create-user-provided-service.html).
+
+We also support standard Dynatrace environment variables.
+
+## Requirements
+
+- Go 1.11
+- Linux to run the tests.
+
+## Development
+
+You can download or clone the repository.
+
+You can run tests through,
+
+```
+go test
+```
+
+If you modify/add interfaces, you may need to regenerate the mocks. For this you need [gomock](https://github.com/golang/mock):
+
+```
+# To download Gomock
+go get github.com/golang/mock/gomock
+go install github.com/golang/mock/mockgen
+
+# To generate the mocks
+go generate
+```
diff --git a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/hook.go b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/hook.go
new file mode 100644
index 00000000..8490ce68
--- /dev/null
+++ b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/hook.go
@@ -0,0 +1,489 @@
+package dynatrace
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "math"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "time"
+
+ "github.com/cloudfoundry/libbuildpack"
+)
+
+// Command is an interface around libbuildpack.Command. Represents an executor for external command calls. We have it
+// as an interface so that we can mock it and use in the unit tests.
+type Command interface {
+ Execute(string, io.Writer, io.Writer, string, ...string) error
+}
+
+// credentials represent the user settings extracted from the environment.
+type credentials struct {
+ ServiceName string
+ EnvironmentID string
+ CustomOneAgentURL string
+ APIToken string
+ APIURL string
+ SkipErrors bool
+ NetworkZone string
+ EnableFIPS bool
+}
+
+// Hook implements libbuildpack.Hook. It downloads and install the Dynatrace OneAgent.
+type Hook struct {
+ libbuildpack.DefaultHook
+ Log *libbuildpack.Logger
+ Command Command
+
+ // IncludeTechnologies is used to indicate the technologies we want to download agents for.
+ IncludeTechnologies []string
+
+ // MaxDownloadRetries is the maximum number of retries the hook will try to download the agent if they fail.
+ MaxDownloadRetries int
+}
+
+// NewHook returns a libbuildpack.Hook instance for integrating monitoring with Dynatrace. The technology names for the
+// agents to download can be set as parameters.
+func NewHook(technologies ...string) libbuildpack.Hook {
+ return &Hook{
+ Log: libbuildpack.NewLogger(os.Stdout),
+ Command: &libbuildpack.Command{},
+ IncludeTechnologies: technologies,
+ MaxDownloadRetries: 3,
+ }
+}
+
+// AfterCompile downloads and installs the Dynatrace agent.
+func (h *Hook) AfterCompile(stager *libbuildpack.Stager) error {
+ // All other methods in this package are called from here, which
+ // makes it the main entry-point.
+
+ var err error
+
+ h.Log.Debug("Checking for enabled dynatrace service...")
+
+ // Get credentials...
+ creds := h.getCredentials()
+ if creds == nil {
+ h.Log.Debug("Dynatrace service credentials not found!")
+ return nil
+ }
+
+ h.Log.Info("Dynatrace service credentials found. Setting up Dynatrace OneAgent.")
+
+ // Get buildpack version and language
+
+ lang := stager.BuildpackLanguage()
+ ver, err := stager.BuildpackVersion()
+ if err != nil {
+ h.Log.Warning("Failed to get buildpack version: %v", err)
+ ver = "unknown"
+ }
+
+ installDir := filepath.Join("dynatrace", "oneagent")
+ err = h.downloadAndInstall(creds, ver, lang, installDir, stager)
+ if err != nil {
+ return err
+ }
+
+ h.Log.Debug("Fetching updated OneAgent configuration from tenant... ")
+ configDir := filepath.Join(stager.BuildDir(), installDir)
+ if err := h.updateAgentConfig(creds, configDir, lang, ver); err != nil {
+ if creds.SkipErrors {
+ h.Log.Warning("Error during agent config update, skipping it")
+ return nil
+ }
+ h.Log.Error("Error during agent config update: %s", err)
+ return err
+
+ }
+
+ if h.getCredentials().EnableFIPS {
+ h.Log.Debug("Removing file 'dt_fips_disabled.flag' to enable FIPS mode...")
+ flagFilePath := filepath.Join(stager.BuildDir(), installDir, "agent", "dt_fips_disabled.flag")
+ if err := os.Remove(flagFilePath); err != nil {
+ h.Log.Error("Error during fips flag file deletion: %s", err)
+ return err
+ }
+ }
+
+ h.Log.Info("Dynatrace OneAgent injection is set up.")
+ return nil
+}
+
+// getCredentials returns the configuration from the environment, or nil if not found. The credentials are represented
+// as a JSON object in the VCAP_SERVICES environment variable.
+func (h *Hook) getCredentials() *credentials {
+ // Represent the structure of the JSON object in VCAP_SERVICES for parsing.
+
+ var vcapServices map[string][]struct {
+ Name string `json:"name"`
+ Credentials map[string]interface{} `json:"credentials"`
+ }
+
+ if err := json.Unmarshal([]byte(os.Getenv("VCAP_SERVICES")), &vcapServices); err != nil {
+ h.Log.Debug("Failed to unmarshal VCAP_SERVICES: %s", err)
+ return nil
+ }
+
+ var found []*credentials
+
+ for _, services := range vcapServices {
+ for _, service := range services {
+ if !strings.Contains(strings.ToLower(service.Name), "dynatrace") {
+ continue
+ }
+
+ queryString := func(key string) string {
+ if value, ok := service.Credentials[key].(string); ok {
+ return value
+ }
+ return ""
+ }
+
+ creds := &credentials{
+ ServiceName: service.Name,
+ EnvironmentID: queryString("environmentid"),
+ APIToken: queryString("apitoken"),
+ APIURL: queryString("apiurl"),
+ CustomOneAgentURL: queryString("customoneagenturl"),
+ SkipErrors: queryString("skiperrors") == "true",
+ NetworkZone: queryString("networkzone"),
+ EnableFIPS: queryString("enablefips") == "true",
+ }
+
+ if (creds.EnvironmentID != "" && creds.APIToken != "") || creds.CustomOneAgentURL != "" {
+ found = append(found, creds)
+ } else if !(creds.EnvironmentID == "" && creds.APIToken == "") { // One of the fields is empty.
+ h.Log.Warning("Incomplete credentials for service: %s, environment ID: %s, API token: %s", creds.ServiceName,
+ creds.EnvironmentID, creds.APIToken)
+ }
+ }
+ }
+
+ if len(found) == 1 {
+ h.Log.Debug("Found one matching service: %s", found[0].ServiceName)
+ return found[0]
+ }
+
+ if len(found) > 1 {
+ h.Log.Warning("More than one matching service found!")
+ }
+
+ return nil
+}
+
+// download gets url, and stores it as filePath, retrying a few more times if the downloads fail.
+func (h *Hook) download(url, filePath string, buildPackVersion string, language string, creds *credentials) error {
+ const baseWaitTime = 3 * time.Second
+
+ client := &http.Client{}
+ req, _ := http.NewRequest("GET", url, nil)
+ if creds.CustomOneAgentURL == "" {
+ req.Header.Set("User-Agent", fmt.Sprintf("cf-%s-buildpack/%s", language, buildPackVersion))
+ req.Header.Set("Authorization", fmt.Sprintf("Api-Token %s", creds.APIToken))
+ }
+
+ out, err := os.Create(filePath)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ for i := 0; ; i++ {
+ resp, err := client.Do(req)
+ if err == nil {
+ // We truncate the file to make it empty, we also need to move the offset to the beginning. For errors
+ // here, these would be unexpected so we just fail the function without retrying.
+
+ if err = out.Truncate(0); err != nil {
+ resp.Body.Close()
+ return err
+ }
+
+ if _, err = out.Seek(0, io.SeekStart); err != nil {
+ resp.Body.Close()
+ return err
+ }
+
+ // Now we copy the response content into the file.
+ _, err = io.Copy(out, resp.Body)
+
+ resp.Body.Close() // Ignore error, nothing worth doing if it fails.
+
+ if resp.StatusCode < 400 && err == nil {
+ return nil
+ }
+
+ h.Log.Debug("Download returned with status %s, error: %v", resp.Status, err)
+
+ if i == h.MaxDownloadRetries {
+ h.Log.Warning("Maximum number of retries attempted: %d", h.MaxDownloadRetries)
+ return fmt.Errorf("download returned with status %s, error: %v", resp.Status, err)
+ }
+ } else {
+ h.Log.Debug("Download failed: %v", err)
+
+ if i == h.MaxDownloadRetries {
+ h.Log.Warning("Maximum number of retries attempted: %d", h.MaxDownloadRetries)
+ return err
+ }
+ }
+
+ waitTime := baseWaitTime + time.Duration(math.Pow(2, float64(i)))*time.Second
+ h.Log.Warning("Error during installer download, retrying in %v", waitTime)
+ time.Sleep(waitTime)
+ }
+}
+
+func (h *Hook) getDownloadURL(c *credentials, osType string, installerType string) string {
+ if c.CustomOneAgentURL != "" {
+ return c.CustomOneAgentURL
+ }
+
+ apiURL, err := h.ensureApiURL(c)
+ if err != nil {
+ return ""
+ }
+
+ u, err := url.ParseRequestURI(fmt.Sprintf("%s/v1/deployment/installer/agent/%s/%s/latest", apiURL, osType, installerType))
+ if err != nil {
+ return ""
+ }
+
+ qv := make(url.Values)
+ qv.Add("bitness", "64")
+ // only set the networkzone property when it is configured
+ if c.NetworkZone != "" {
+ qv.Add("networkZone", c.NetworkZone)
+ }
+ for _, t := range h.IncludeTechnologies {
+ qv.Add("include", t)
+ }
+ u.RawQuery = qv.Encode() // Parameters will be sorted by key.
+
+ return u.String()
+}
+
+// ensureApiURL makes sure that a valid URL was provided via the cf service.
+// If the c.APIURL property is empty, we assume this is a PaaS setting and generate
+// a proper API URL for a PaaS tenant.
+func (h *Hook) ensureApiURL(creds *credentials) (string, error) {
+ apiURL := creds.APIURL
+ if apiURL == "" {
+ apiURL = fmt.Sprintf("https://%s.live.dynatrace.com/api", creds.EnvironmentID)
+ h.Log.Debug("No apiurl configured, assuming PaaS tenant and setting apiurl to %s", apiURL)
+ } else {
+ h.Log.Debug("apiurl parameter configured is set to %s, no need to apply PaaS fallback", apiURL)
+ }
+
+ url, err := url.ParseRequestURI(apiURL)
+ if err != nil {
+ h.Log.Error("Failed to verify the configured API URL: %s", err)
+ return "", err
+ }
+
+ return url.String(), nil
+}
+
+// findAgentPath reads the manifest file included in the OneAgent package, and looks
+// for the process agent file path.
+func (h *Hook) findAgentPath(installDir string, technology string, binaryType string, libraryFilename string, platformName string) (string, error) {
+ // With these classes, we try to replicate the structure for the manifest.json file, so that we can parse it.
+
+ type Binary struct {
+ Path string `json:"path"`
+ BinaryType string `json:"binarytype"`
+ }
+
+ type Architecture map[string][]Binary
+ type Technologies map[string]Architecture
+
+ type Manifest struct {
+ Technologies Technologies `json:"technologies"`
+ }
+
+ fallbackPath := filepath.Join("agent", "lib64", libraryFilename)
+
+ manifestPath := filepath.Join(installDir, "manifest.json")
+ if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
+ h.Log.Info("manifest.json not found, using fallback!")
+ return fallbackPath, nil
+ }
+
+ var manifest Manifest
+
+ if raw, err := ioutil.ReadFile(manifestPath); err != nil {
+ return "", err
+ } else if err = json.Unmarshal(raw, &manifest); err != nil {
+ return "", err
+ }
+
+ for _, binary := range manifest.Technologies[technology][platformName] {
+ if binary.BinaryType == binaryType {
+ return binary.Path, nil
+ }
+ }
+
+ // Using fallback path if we don't find the 'primary' process agent.
+ h.Log.Warning("Agent path not found in manifest.json, using fallback!")
+ return fallbackPath, nil
+}
+
+// Downloads most recent agent config from configuration API of the tenant
+// and merges it with the local version the standalone installer package brings along.
+func (h *Hook) updateAgentConfig(creds *credentials, installDir, buildPackLanguage, buildPackVersion string) error {
+ // agentConfigProperty represents a line of raw data we get from the config api
+ type agentConfigProperty struct {
+ Section string
+ Key string
+ Value string
+ }
+
+ // Container type for agentConfigProperty.
+ // Used for easy unmarshalling.
+ type properties struct {
+ Properties []agentConfigProperty
+ }
+
+ // Fetch most recent OneAgent config from API, which we get back in JSON format
+ // According to the API spec it always returns at least some sort of Header Info.
+ // So, we do not need to handle the case that the request succeeds and the content is empty.
+ client := &http.Client{Timeout: 3 * time.Second}
+ apiURL, err := h.ensureApiURL(creds)
+ if err != nil {
+ return err
+ }
+ agentConfigUrl := apiURL + "/v1/deployment/installer/agent/processmoduleconfig"
+
+ h.Log.Debug("Downloading updated OneAgent config from %s", agentConfigUrl)
+ req, _ := http.NewRequest("GET", agentConfigUrl, nil)
+ req.Header.Set("User-Agent", fmt.Sprintf("cf-%s-buildpack/%s", buildPackLanguage, buildPackVersion))
+ req.Header.Set("Authorization", fmt.Sprintf("Api-Token %s", creds.APIToken))
+ client.Do(req)
+ resp, err := client.Do(req)
+
+ configComment := ""
+ configFromAPI := make(map[string]map[string]string)
+ if err != nil || resp.StatusCode != 200 {
+ h.Log.Warning("Failed to fetch updated OneAgent config from the API")
+ configComment = "# Warning: Failed to fetch updated OneAgent config from the API. This config only includes settings provided by the installer.\n"
+ } else {
+ h.Log.Debug("Successfully fetched updated OneAgent config from the API")
+ configComment = "# This config is a merge between the installer and the Cluster config\n"
+ var jsonConfig properties
+ json.NewDecoder(resp.Body).Decode(&jsonConfig)
+
+ for _, v := range jsonConfig.Properties {
+ // you gotta check if the required map is already there
+ // if not: initialize it with a nice make :-)
+ _, ok := configFromAPI[v.Section]
+ if !ok {
+ configFromAPI[v.Section] = make(map[string]string)
+ }
+ configFromAPI[v.Section][v.Key] = v.Value
+ }
+ }
+
+ // read data from ruxitagentproc.conf file
+ agentConfigPath := filepath.Join(installDir, "agent", "conf", "ruxitagentproc.conf")
+ agentConfigFile, err := os.Open(agentConfigPath)
+ if err != nil {
+ h.Log.Error("Failure while reading OneAgent config file %s: %s", agentConfigPath, err)
+ return err
+ }
+ h.Log.Debug("Successfully read OneAgent config from %s", agentConfigPath)
+ defer agentConfigFile.Close()
+
+ configFromAgent := make(map[string]map[string]string)
+ currentSection := ""
+ var configSection string
+ var sectionRegexp, _ = regexp.Compile(`\[(.*)\]`)
+ configScanner := bufio.NewScanner(agentConfigFile)
+
+ h.Log.Debug("Starting to parse OneAgent config...")
+ for configScanner.Scan() {
+ // This parses the data we retrieved from ruxitagentproc.conf and stores
+ // it into the configFromAgent map of maps that was created above, for easy
+ // merging with configFromAPI later on.
+ currentLine := configScanner.Text()
+
+ // Check if current line is a section header
+ if sectionHeader := sectionRegexp.FindStringSubmatch(currentLine); len(sectionHeader) != 0 {
+ configSection = sectionHeader[1]
+ } else {
+ configSection = ""
+ }
+
+ if configSection != "" {
+ currentSection = configSection
+ } else if strings.HasPrefix(currentLine, "#") { //it's a comment line
+ // skipping over lines that are purely comments
+ continue
+ } else if currentLine == "" {
+ // skipping over empty lines
+ continue
+ } else {
+ // you gotta check if the required map is already there
+ // if not: initialize it with a nice make :-)
+ _, ok := configFromAgent[currentSection]
+ if !ok {
+ configFromAgent[currentSection] = make(map[string]string)
+ }
+ configLineKey := strings.Fields(currentLine)[0]
+ configLineValue := strings.Join(strings.Fields(currentLine)[1:], " ")
+ configFromAgent[currentSection][configLineKey] = configLineValue
+ }
+ }
+ h.Log.Debug("Successfully parsed OneAgent config...")
+
+ // Merge the two configs to get an updated version.
+ // Just writes all of configFromAPI over eventually existing values in
+ // configFromAgent, since the ones from the API are supposed to be the recent ones.
+ // This includes adding possibly new sections and/or property keys.
+ h.Log.Debug("Starting with OneAgent configuration merging...")
+ for section := range configFromAPI {
+ for property := range configFromAPI[section] {
+ _, ok := configFromAgent[section]
+ if !ok {
+ configFromAgent[section] = make(map[string]string)
+ }
+ configFromAgent[section][property] = configFromAPI[section][property]
+ }
+ }
+ h.Log.Debug("Finished OneAgent configuration merging")
+
+ // open ruxitagentproc.conf to overwrite its content
+ overwriteAgentConfigFile, err := os.Create(agentConfigPath)
+ if err != nil {
+ h.Log.Error("Error opening OneAgent config file %s: %s", agentConfigPath, err)
+ return err
+ }
+ h.Log.Debug("Successfully opened OneAgent config file %s for writing", agentConfigPath)
+ defer overwriteAgentConfigFile.Close()
+
+ // Write additional comments to the config
+ fmt.Fprintf(overwriteAgentConfigFile, configComment)
+
+ // write merged data to ruxitagentproc.conf
+ for section := range configFromAgent {
+ fmt.Fprintf(overwriteAgentConfigFile, "[%s]\n", section)
+ for k, v := range configFromAgent[section] {
+ fmt.Fprintf(overwriteAgentConfigFile, "%s %s\n", k, v)
+ }
+
+ // Trailing empty newline at the end of each section for better human readability
+ fmt.Fprintf(overwriteAgentConfigFile, "\n")
+ }
+
+ h.Log.Debug("Finished writing updated OneAgent config back to %s", agentConfigPath)
+
+ return nil
+}
diff --git a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/install_unix.go b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/install_unix.go
new file mode 100644
index 00000000..6a05b7fe
--- /dev/null
+++ b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/install_unix.go
@@ -0,0 +1,105 @@
+//go:build !windows
+// +build !windows
+
+package dynatrace
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/cloudfoundry/libbuildpack"
+)
+
+func (h *Hook) downloadAndInstall(creds *credentials, ver string, lang string, installDir string, stager *libbuildpack.Stager) error {
+ installerFilePath := filepath.Join(os.TempDir(), "paasInstaller.sh")
+ url := h.getDownloadURL(creds, "unix", "paas-sh")
+
+ h.Log.Info("Downloading '%s' to '%s'", url, installerFilePath)
+ err := h.download(url, installerFilePath, ver, lang, creds)
+ if err != nil {
+ if creds.SkipErrors {
+ h.Log.Warning("Error during installer download, skipping installation")
+ return nil
+ }
+ return err
+ }
+
+ // Run installer...
+
+ h.Log.Debug("Making %s executable...", installerFilePath)
+ os.Chmod(installerFilePath, 0755)
+
+ h.Log.BeginStep("Starting Dynatrace OneAgent installer")
+
+ if os.Getenv("BP_DEBUG") != "" {
+ err = h.Command.Execute("", os.Stdout, os.Stderr, installerFilePath, stager.BuildDir())
+ } else {
+ err = h.Command.Execute("", ioutil.Discard, ioutil.Discard, installerFilePath, stager.BuildDir())
+ }
+ if err != nil {
+ return err
+ }
+
+ h.Log.Info("Dynatrace OneAgent installed.")
+
+ // Post-installation setup...
+
+ dynatraceEnvName := "dynatrace-env.sh"
+ dynatraceEnvPath := filepath.Join(stager.DepDir(), "profile.d", dynatraceEnvName)
+ agentLibPath, err := h.findAgentPath(filepath.Join(stager.BuildDir(), installDir), "process", "primary", "liboneagentproc.so", "linux-x86-64")
+ if err != nil {
+ h.Log.Error("Manifest handling failed!")
+ return err
+ }
+
+ agentLibPath = filepath.Join(installDir, agentLibPath)
+ agentBuilderLibPath := filepath.Join(stager.BuildDir(), agentLibPath)
+
+ if _, err = os.Stat(agentBuilderLibPath); os.IsNotExist(err) {
+ h.Log.Error("Agent library (%s) not found!", agentBuilderLibPath)
+ return err
+ }
+
+ h.Log.BeginStep("Setting up Dynatrace OneAgent injection...")
+ h.Log.Debug("Copy %s to %s", dynatraceEnvName, dynatraceEnvPath)
+ if err = libbuildpack.CopyFile(filepath.Join(stager.BuildDir(), installDir, dynatraceEnvName), dynatraceEnvPath); err != nil {
+ return err
+ }
+
+ h.Log.Debug("Open %s for modification...", dynatraceEnvPath)
+ f, err := os.OpenFile(dynatraceEnvPath, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
+ if err != nil {
+ return err
+ }
+
+ defer f.Close()
+
+ extra := ""
+
+ h.Log.Debug("Setting LD_PRELOAD...")
+ extra += fmt.Sprintf("\nexport LD_PRELOAD=${HOME}/%s", agentLibPath)
+
+ if creds.NetworkZone != "" {
+ h.Log.Debug("Setting DT_NETWORK_ZONE...")
+ extra += fmt.Sprintf("\nexport DT_NETWORK_ZONE=${DT_NETWORK_ZONE:-%s}", creds.NetworkZone)
+ }
+
+ // By default, OneAgent logs are printed to stderr. If the customer doesn't override this behavior through an
+ // environment variable, then we change the default output to stdout.
+ if os.Getenv("DT_LOGSTREAM") == "" {
+ h.Log.Debug("Setting DT_LOGSTREAM to stdout...")
+ extra += "\nexport DT_LOGSTREAM=stdout"
+ }
+
+ h.Log.Debug("Preparing custom properties...")
+ extra += fmt.Sprintf(
+ "\nexport DT_CUSTOM_PROP=\"${DT_CUSTOM_PROP} CloudFoundryBuildpackLanguage=%s CloudFoundryBuildpackVersion=%s\"", lang, ver)
+
+ if _, err = f.WriteString(extra); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/install_windows.go b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/install_windows.go
new file mode 100644
index 00000000..9943c99b
--- /dev/null
+++ b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/install_windows.go
@@ -0,0 +1,111 @@
+//go:build windows
+// +build windows
+
+package dynatrace
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "slices"
+ "strings"
+
+ "github.com/cloudfoundry/libbuildpack"
+)
+
+func (h *Hook) downloadAndInstall(creds *credentials, ver string, lang string, installDir string, stager *libbuildpack.Stager) error {
+ installerFilePath := filepath.Join(os.TempDir(), "paasInstaller.zip")
+ url := h.getDownloadURL(creds, "windows", "paas")
+
+ h.Log.Info("Downloading '%s' to '%s'", url, installerFilePath)
+ if err := h.download(url, installerFilePath, ver, lang, creds); err != nil {
+ if creds.SkipErrors {
+ h.Log.Warning("Error during installer download, skipping installation")
+ return nil
+ }
+ return err
+ }
+
+ // Do manual installation...
+
+ h.Log.BeginStep("Starting Dynatrace OneAgent installation")
+
+ h.Log.Info("Unzipping archive '%s' to '%s'", installerFilePath, filepath.Join(stager.BuildDir(), installDir))
+ err := libbuildpack.ExtractZip(installerFilePath, filepath.Join(stager.BuildDir(), installDir))
+ if err != nil {
+ h.Log.Error("Error during unzipping paas archive")
+ return err
+ }
+
+ h.Log.Info("Dynatrace OneAgent installed.")
+
+ // Post-installation setup...
+
+ h.Log.BeginStep("Setting up Dynatrace OneAgent injection...")
+ if slices.Contains(h.IncludeTechnologies, "dotnet") {
+ err = h.setUpDotNetCorProfilerInjection(creds, ver, lang, installDir, stager)
+ } else {
+ h.Log.Warning("No injection method available for technology stack")
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (h *Hook) setUpDotNetCorProfilerInjection(creds *credentials, ver string, lang string, installDir string, stager *libbuildpack.Stager) error {
+ loaderPath, err := h.findAbsoluteLoaderPath(stager, installDir)
+ if err != nil {
+ return fmt.Errorf("cannot find oneagentloader.dll: %s", err)
+ }
+
+ scriptContent := "set COR_ENABLE_PROFILING=1\n"
+ scriptContent += "set COR_PROFILER={B7038F67-52FC-4DA2-AB02-969B3C1EDA03}\n"
+ scriptContent += "set DT_AGENTACTIVE=true\n"
+ scriptContent += "set DT_BLOCKLIST=powershell*\n"
+ scriptContent += fmt.Sprintf("set COR_PROFILER_PATH_64=%s\n", loaderPath)
+
+ if creds.NetworkZone != "" {
+ h.Log.Debug("Setting DT_NETWORK_ZONE...")
+ scriptContent += "set DT_NETWORK_ZONE=" + creds.NetworkZone + "\n"
+ }
+
+ h.Log.Debug("Preparing custom properties...")
+ scriptContent += fmt.Sprintf("set DT_CUSTOM_PROP=\"%%DT_CUSTOM_PROP%% CloudFoundryBuildpackLanguage=%s CloudFoundryBuildpackVersion=%s\"\n", lang, ver)
+
+ stager.WriteProfileD("dynatrace-env.cmd", scriptContent)
+
+ return nil
+}
+
+func (h *Hook) findAbsoluteLoaderPath(stager *libbuildpack.Stager, installDir string) (string, error) {
+
+ // look for dotnet loader DLL file relative to the root of the downloaded zip archive
+ // and get the path from the manifest e.g. agent/bin/windows-x86-64/oneagentloader.dll
+ loaderDllPath, err := h.findAgentPath(filepath.Join(stager.BuildDir(), installDir), "dotnet", "loader", "oneagentloader.dll", "windows-x86-64")
+ if err != nil {
+ h.Log.Error("Manifest handling failed!")
+ return "", err
+ }
+
+ // windows path separator is "\" instead of "/"
+ loaderDllPath = strings.ReplaceAll(loaderDllPath, "/", "\\")
+
+ // build the loader DLL path relative to the app directory
+ // e.g. dynatrace/oneagent/agent/bin/windows-x86-64/oneagentloader.dll
+ loaderDllPathInAppDir := filepath.Join(installDir, loaderDllPath)
+
+ // check that the loader dll is present in the build dir
+ // e.g. at \tmp\app\dynatrace\oneagent\agent\bin\1.303.0.20240930-081133\windows-x86-32\oneagentloader.dll
+ loaderDllPathInBuildDir := filepath.Join(stager.BuildDir(), loaderDllPathInAppDir)
+
+ if _, err = os.Stat(loaderDllPathInBuildDir); os.IsNotExist(err) {
+ h.Log.Error("Agent library (%s) not found!", loaderDllPathInBuildDir)
+ return "", err
+ }
+
+ // build the absolute path of the loader DLL as it will be available at runtime
+ return filepath.Join("C:\\users\\vcap\\app", loaderDllPathInAppDir), nil
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index d1e2dee2..b11458b2 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1,6 +1,9 @@
# code.cloudfoundry.org/lager v2.0.0+incompatible
## explicit
code.cloudfoundry.org/lager
+# github.com/Dynatrace/libbuildpack-dynatrace v1.7.0
+## explicit; go 1.19
+github.com/Dynatrace/libbuildpack-dynatrace
# github.com/Masterminds/semver v1.5.0
## explicit
github.com/Masterminds/semver