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