diff --git a/go.mod b/go.mod index f5933bf0b..05b70079c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/cloudfoundry/python-buildpack go 1.24.0 require ( - github.com/Dynatrace/libbuildpack-dynatrace v1.8.0 + github.com/Dynatrace/libbuildpack-dynatrace v1.10.0 github.com/blang/semver v3.5.1+incompatible github.com/cloudfoundry/libbuildpack v0.0.0-20260306125332-dcaf55eb6f33 github.com/cloudfoundry/switchblade v0.9.5 diff --git a/go.sum b/go.sum index 537e7c725..a7a1d043a 100644 --- a/go.sum +++ b/go.sum @@ -560,8 +560,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/CycloneDX/cyclonedx-go v0.7.1/go.mod h1:N/nrdWQI2SIjaACyyDs/u7+ddCkyl/zkNs8xFsHF2Ps= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/Dynatrace/libbuildpack-dynatrace v1.8.0 h1:VNcd8+rurUUdY12emGfLGUUj5cMH4hkNgrdk8LO3dHE= -github.com/Dynatrace/libbuildpack-dynatrace v1.8.0/go.mod h1:Uu9aa5UFAk1Ua+zZXnvzo+avDXuEi+GtegeOyja9xg4= +github.com/Dynatrace/libbuildpack-dynatrace v1.10.0 h1:8S8wgGPNgHamzaX+zzUjVPkjhWlRPxQC6nV7ChHHd+o= +github.com/Dynatrace/libbuildpack-dynatrace v1.10.0/go.mod h1:Uu9aa5UFAk1Ua+zZXnvzo+avDXuEi+GtegeOyja9xg4= github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= diff --git a/src/python/hooks/dynatrace.go b/src/python/hooks/dynatrace.go index 7585289ad..c69553297 100644 --- a/src/python/hooks/dynatrace.go +++ b/src/python/hooks/dynatrace.go @@ -6,5 +6,5 @@ import ( ) func init() { - libbuildpack.AddHook(dynatrace.NewHook("sdk", "process")) + libbuildpack.AddHook(dynatrace.NewHook("python", "sdk", "process")) } diff --git a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/.gitignore b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/.gitignore index 31c5c6ff2..c57d01cee 100644 --- a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/.gitignore +++ b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/.gitignore @@ -11,5 +11,6 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out -# vim session file +# various editor files Session.vim +*.swp diff --git a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/CODEOWNERS b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/CODEOWNERS new file mode 100644 index 000000000..eed8c20a1 --- /dev/null +++ b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/CODEOWNERS @@ -0,0 +1 @@ +* @Dynatrace/team-kite diff --git a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/README.md b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/README.md index c85864380..b510233ea 100644 --- a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/README.md +++ b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/README.md @@ -21,7 +21,12 @@ func init() { ## 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. +The Hook will look for credentials in the configurations for existing services. It searches for service credentials depending on the binding type: + +1. **File-based**: If the `VCAP_SERVICES_FILE_PATH` environment variable is set, the hook reads the VCAP_SERVICES JSON from the file at that path. +2. **Environment variable**: If the env var `VCAP_SERVICES` is set, it reads the JSON from the `VCAP_SERVICES` environment variable directly. + +In both cases, we look for service names having the 'dynatrace' substring. We support the following configuration fields, @@ -33,6 +38,7 @@ We support the following configuration fields, | 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 | +| removetechnologies| string | Removes OneAgent code-modules via a comma-separated list. This is applied **before** addtechnologies. | No | empty | | addtechnologies| string | Adds additional OneAgent code-modules via a comma-separated list. See [supported values](https://docs.dynatrace.com/docs/dynatrace-api/environment-api/deployment/oneagent/download-oneagent-version#parameters) in the "included" row | No | empty | For example, @@ -47,8 +53,9 @@ We also support standard Dynatrace environment variables. ## Requirements -- Go 1.11 -- Linux to run the tests. +- Go 1.19 or higher. +- Deployment targets: Linux and Windows. +- Development and testing: Linux, Mac OS, and Windows. ## Development @@ -57,7 +64,14 @@ You can download or clone the repository. You can run tests through, ``` -go test +go test ./... +``` + +By default, tests simulate the Linux platform. To test against a different target OS, use the `-os` flag: + +``` +go test ./... -os=windows +go test ./... -os=linux ``` If you modify/add interfaces, you may need to regenerate the mocks. For this you need [gomock](https://github.com/golang/mock): diff --git a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/hook.go b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/hook.go index bef46b816..936aec74d 100644 --- a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/hook.go +++ b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/hook.go @@ -13,6 +13,7 @@ import ( "path/filepath" "regexp" "runtime" + "slices" "strings" "time" @@ -36,6 +37,7 @@ type credentials struct { NetworkZone string EnableFIPS bool AddTechnologies string + RemoveTechnologies string } // Hook implements libbuildpack.Hook. It downloads and install the Dynatrace OneAgent. @@ -66,7 +68,12 @@ func NewHook(technologies ...string) libbuildpack.Hook { 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. + return h.injectDynatrace(stager, runtime.GOOS) +} + +// injectDynatrace is an indirection to get rid of the tight coupling to the underlying operating system +func (h *Hook) injectDynatrace(stager *libbuildpack.Stager, operatingSystem string) error { var err error h.Log.Debug("Checking for enabled dynatrace service...") @@ -84,30 +91,30 @@ func (h *Hook) AfterCompile(stager *libbuildpack.Stager) error { // download installer var installerFilename string - if runtime.GOOS == "linux" { + if operatingSystem == "linux" { installerFilename = "paasInstaller.sh" - } else if runtime.GOOS == "windows" { + } else if operatingSystem == "windows" { installerFilename = "paasInstaller.zip" } else { // This is the only place where we need to return an error. // All following operating system checks are just to determine installation specifics. - return errors.New("libbuildpack-dynatrace: Unsupported operating system: " + runtime.GOOS) + return errors.New("libbuildpack-dynatrace: Unsupported operating system: " + operatingSystem) } installerFilePath := filepath.Join(os.TempDir(), installerFilename) - url := h.getDownloadURL(creds) + url := h.getDownloadURL(creds, operatingSystem) err = h.download(url, installerFilePath, stager, creds) if err != nil && creds.SkipErrors { h.Log.Warning("Error during installer download, skipping installation") return nil - }else if err != nil { + } else if err != nil { return err } // run installer - if runtime.GOOS == "linux" { + if operatingSystem == "linux" { err = h.runInstallerUnix(installerFilePath, installDir, creds, stager) - } else if runtime.GOOS == "windows" { + } else if operatingSystem == "windows" { err = h.runInstallerWindows(installerFilePath, installDir, creds, stager) } @@ -137,9 +144,45 @@ func (h *Hook) AfterCompile(stager *libbuildpack.Stager) error { return nil } +// loadVCAPServicesData returns the raw VCAP_SERVICES JSON data from the appropriate source. +func (h *Hook) loadVCAPServicesData() []byte { + filePath, filePathSet := os.LookupEnv("VCAP_SERVICES_FILE_PATH") + + if filePathSet { + if filePath == "" { + h.Log.Debug("VCAP_SERVICES_FILE_PATH is set but empty") + return nil + } + + h.Log.Debug("Loading VCAP services from file: %s", filePath) + fileContent, err := os.ReadFile(filePath) + if err != nil { + h.Log.Error("Failed to read VCAP services file %s: %s", filePath, err) + return nil + } + h.Log.Debug("Successfully read VCAP Service data.") + return fileContent + + } + + h.Log.Debug("Loading VCAP services from environment variable VCAP_SERVICES") + envData := os.Getenv("VCAP_SERVICES") + if envData == "" { + h.Log.Debug("Environment variable VCAP_SERVICES is not set or empty") + return nil + } + h.Log.Debug("Successfully read VCAP Service data from environment variable.") + return []byte(envData) +} + // 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. +// as a JSON object loaded via loadVCAPServicesData. func (h *Hook) getCredentials() *credentials { + data := h.loadVCAPServicesData() + if data == nil { + return nil + } + // Represent the structure of the JSON object in VCAP_SERVICES for parsing. var vcapServices map[string][]struct { @@ -147,7 +190,7 @@ func (h *Hook) getCredentials() *credentials { Credentials map[string]interface{} `json:"credentials"` } - if err := json.Unmarshal([]byte(os.Getenv("VCAP_SERVICES")), &vcapServices); err != nil { + if err := json.Unmarshal(data, &vcapServices); err != nil { h.Log.Debug("Failed to unmarshal VCAP_SERVICES: %s", err) return nil } @@ -176,13 +219,14 @@ func (h *Hook) getCredentials() *credentials { SkipErrors: queryString("skiperrors") == "true", NetworkZone: queryString("networkzone"), EnableFIPS: queryString("enablefips") == "true", - AddTechnologies: queryString("addtechnologies"), + AddTechnologies: queryString("addtechnologies"), + RemoveTechnologies: queryString("removetechnologies"), } 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, + h.Log.Error("Incomplete credentials for service: %s, environment ID: %s, API token: %s", creds.ServiceName, creds.EnvironmentID, creds.APIToken) } } @@ -194,7 +238,7 @@ func (h *Hook) getCredentials() *credentials { } if len(found) > 1 { - h.Log.Warning("More than one matching service found!") + h.Log.Error("More than one matching service found!") } return nil @@ -206,10 +250,10 @@ func (h *Hook) download(url, filePath string, stager *libbuildpack.Stager, creds req, _ := http.NewRequest("GET", url, nil) if creds.CustomOneAgentURL == "" { ver, err := stager.BuildpackVersion() - if err != nil { - h.Log.Warning("Failed to get buildpack version: %v", err) - ver = "unknown" - } + if err != nil { + h.Log.Warning("Failed to get buildpack version: %v", err) + ver = "unknown" + } req.Header.Set("User-Agent", fmt.Sprintf("cf-%s-buildpack/%s", stager.BuildpackLanguage(), ver)) req.Header.Set("Authorization", fmt.Sprintf("Api-Token %s", creds.APIToken)) } @@ -219,7 +263,7 @@ func (h *Hook) download(url, filePath string, stager *libbuildpack.Stager, creds return err } defer out.Close() - + const baseWaitTime = 3 * time.Second for i := 0; ; i++ { resp, err := client.Do(req) @@ -268,12 +312,13 @@ func (h *Hook) download(url, filePath string, stager *libbuildpack.Stager, creds } -func (h *Hook) getDownloadURL(c *credentials) string { +func (h *Hook) getDownloadURL(c *credentials, operatingSystem string) string { var osType, installerType string - if runtime.GOOS == "linux" { + switch operatingSystem { + case "linux": osType = "unix" installerType = "paas-sh" - } else if runtime.GOOS == "windows" { + case "windows": osType = "windows" installerType = "paas" } @@ -298,9 +343,25 @@ func (h *Hook) getDownloadURL(c *credentials) string { if c.NetworkZone != "" { qv.Add("networkZone", c.NetworkZone) } + + keepTech := []string{} + if c.RemoveTechnologies != "" { + // remove configured OneAgent code modules, except 'process' + remove := strings.Split(c.RemoveTechnologies, ",") + for _, tech := range h.IncludeTechnologies { + if tech == "process" || !slices.Contains(remove, tech) { + keepTech = append(keepTech, tech) + } else { + h.Log.Debug("Removing code module from download: %s", tech) + } + } + h.IncludeTechnologies = keepTech + } + for _, t := range h.IncludeTechnologies { qv.Add("include", t) } + if c.AddTechnologies != "" { // add optionally configured OneAgent code modules for _, t := range strings.Split(c.AddTechnologies, ",") { @@ -308,6 +369,7 @@ func (h *Hook) getDownloadURL(c *credentials) string { qv.Add("include", t) } } + u.RawQuery = qv.Encode() // Parameters will be sorted by key. return u.String() diff --git a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/windows.go b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/windows.go index 2763fb11f..2301e5913 100644 --- a/vendor/github.com/Dynatrace/libbuildpack-dynatrace/windows.go +++ b/vendor/github.com/Dynatrace/libbuildpack-dynatrace/windows.go @@ -39,16 +39,16 @@ func (h *Hook) runInstallerWindows(installerFilePath, installDir string, creds * } func (h *Hook) setUpDotNetCorProfilerInjection(creds *credentials, installDir string, stager *libbuildpack.Stager) error { - loaderPath, err := h.findAbsoluteLoaderPath(stager, installDir) + agentPath, err := h.findAbsoluteAgentPath(stager, installDir) if err != nil { - return fmt.Errorf("cannot find oneagentloader.dll: %s", err) + return fmt.Errorf("cannot find oneagentdotnet.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) + scriptContent += fmt.Sprintf("set COR_PROFILER_PATH_64=%s\n", agentPath) if creds.NetworkZone != "" { h.Log.Debug("Setting DT_NETWORK_ZONE...") @@ -68,32 +68,32 @@ func (h *Hook) setUpDotNetCorProfilerInjection(creds *credentials, installDir st return nil } -func (h *Hook) findAbsoluteLoaderPath(stager *libbuildpack.Stager, installDir string) (string, error) { +func (h *Hook) findAbsoluteAgentPath(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") + // look for dotnet agent 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/oneagentdotnet .dll + agentDllPath, err := h.findAgentPath(filepath.Join(stager.BuildDir(), installDir), "dotnet", "primary", "oneagentdotnet.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, "/", "\\") + agentDllPath = strings.ReplaceAll(agentDllPath, "/", "\\") - // 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) + // build the agent DLL path relative to the app directory + // e.g. dynatrace/oneagent/agent/bin/windows-x86-64/oneagentdotnet.dll + agentDllPathInAppDir := filepath.Join(installDir, agentDllPath) - // 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) + // check that the agent dll is present in the build dir + // e.g. at \tmp\app\dynatrace\oneagent\agent\bin\1.303.0.20240930-081133\windows-x86-32\oneagentdotnet.dll + agentDllPathInBuildDir := filepath.Join(stager.BuildDir(), agentDllPathInAppDir) - if _, err = os.Stat(loaderDllPathInBuildDir); os.IsNotExist(err) { - h.Log.Error("Agent library (%s) not found!", loaderDllPathInBuildDir) + if _, err = os.Stat(agentDllPathInBuildDir); os.IsNotExist(err) { + h.Log.Error("Agent library (%s) not found!", agentDllPathInBuildDir) 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 + // build the absolute path of the agent DLL as it will be available at runtime + return filepath.Join("C:\\users\\vcap\\app", agentDllPathInAppDir), nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 9803a1092..e3af6aa52 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,7 +1,7 @@ # code.cloudfoundry.org/lager v2.0.0+incompatible ## explicit code.cloudfoundry.org/lager -# github.com/Dynatrace/libbuildpack-dynatrace v1.8.0 +# github.com/Dynatrace/libbuildpack-dynatrace v1.10.0 ## explicit; go 1.19 github.com/Dynatrace/libbuildpack-dynatrace # github.com/Masterminds/semver v1.5.0