Skip to content

Commit 7820238

Browse files
authored
Merge pull request #1149 from stokpop/feature/go-migration
[go-migration] add cf-metrics-exporter framework
2 parents 47a747a + 8bbd424 commit 7820238

7 files changed

Lines changed: 411 additions & 2 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# cf-metrics-exporter (Agent Mode)
2+
3+
This framework integrates the [cf-metrics-exporter](https://github.com/rabobank/cf-metrics-exporter) as a Java agent in the Java buildpack.
4+
5+
## Enabling the Exporter
6+
7+
Set the following environment variable in the cloud foundry env to enable the agent (via manifest.yml or `cf set-env`):
8+
9+
```
10+
CF_METRICS_EXPORTER_ENABLED=true
11+
```
12+
13+
## Configuration
14+
15+
- **CF_METRICS_EXPORTER_ENABLED**: Set to `true` to enable the agent (default: disabled).
16+
- **CF_METRICS_EXPORTER_PROPS**: (Optional) Properties string to pass to the agent, e.g. `enableLogEmitter,rpsType=tomcat-bean`.
17+
18+
## How it Works
19+
20+
- The agent JAR is downloaded during the buildpack supply phase.
21+
- The agent is injected into the JVM at runtime using the `-javaagent` option.
22+
- If `CF_METRICS_EXPORTER_PROPS` is set, its value is appended to the `-javaagent` option.
23+
24+
## Example
25+
26+
```
27+
CF_METRICS_EXPORTER_ENABLED=true
28+
CF_METRICS_EXPORTER_PROPS="enableLogEmitter,rpsType=tomcat-bean"
29+
```
30+
31+
## Version
32+
33+
- Default version: 0.7.1
34+
- Default download URI: https://github.com/rabobank/cf-metrics-exporter/releases/download/0.7.1/cf-metrics-exporter-0.7.1.jar
35+
36+
## Notes
37+
38+
- The agent is injected with priority 43 in JAVA_OPTS (after other APM agents).
39+
40+

manifest.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ default_versions:
8080
version: 7.x
8181
- name: newrelic
8282
version: 8.x
83+
- name: cf-metrics-exporter
84+
version: 0.7.x
8385

8486
url_to_dependency_map:
8587
- match: openjdk-jre-(\d+\.\d+\.\d+)
@@ -544,6 +546,14 @@ dependencies:
544546
cf_stacks:
545547
- cflinuxfs4
546548

549+
# cf-metrics-exporter Agent
550+
- name: cf-metrics-exporter
551+
version: 0.7.1
552+
uri: https://repo1.maven.org/maven2/io/github/rabobank/cf-metrics-exporter/0.7.1/cf-metrics-exporter-0.7.1.jar
553+
sha256: 7ebabd3ffd812082cf92a513c8d2ac52906f5b42cd952cbe740bd5d5b086e79b
554+
cf_stacks:
555+
- cflinuxfs4
556+
547557
# Container Security Provider
548558
# Note: Always enabled by default, provides container-specific security context
549559
- name: container-security-provider

src/integration/frameworks_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,45 @@ func testFrameworks(platform switchblade.Platform, fixtures string) func(*testin
594594
Eventually(deployment).Should(matchers.Serve(ContainSubstring("")))
595595
})
596596
})
597+
598+
context("with CF Metrics Exporter enabled", func() {
599+
it("detects and enables CF Metrics Exporter", func() {
600+
deployment, logs, err := platform.Deploy.
601+
WithEnv(map[string]string{
602+
"BP_JAVA_VERSION": "11",
603+
"CF_METRICS_EXPORTER_ENABLED": "true",
604+
}).
605+
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged"))
606+
Expect(err).NotTo(HaveOccurred(), logs.String)
607+
608+
// Log should indicate exporter enabled with version
609+
Expect(logs.String()).To(ContainSubstring("CF Metrics Exporter v"))
610+
Expect(logs.String()).To(ContainSubstring("enabled"))
611+
Eventually(deployment).Should(matchers.Serve(ContainSubstring("")))
612+
})
613+
614+
it("log includes properties when provided", func() {
615+
props := "debug,enableLogEmitter,intervalSeconds=30"
616+
deployment, logs, err := platform.Deploy.
617+
WithEnv(map[string]string{
618+
"BP_JAVA_VERSION": "11",
619+
"CF_METRICS_EXPORTER_ENABLED": "true",
620+
"CF_METRICS_EXPORTER_PROPS": props,
621+
}).
622+
Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged"))
623+
Expect(err).NotTo(HaveOccurred(), logs.String)
624+
625+
// Should still show exporter enabled
626+
Expect(logs.String()).To(ContainSubstring("CF Metrics Exporter v"))
627+
Expect(logs.String()).To(ContainSubstring("enabled"))
628+
// And include provided properties in logs/configuration output
629+
Expect(logs.String()).To(Or(
630+
ContainSubstring("CF_METRICS_EXPORTER_PROPS"),
631+
ContainSubstring(props),
632+
))
633+
Eventually(deployment).Should(matchers.Serve(ContainSubstring("")))
634+
})
635+
})
597636
})
598637

599638
context("Testing & Code Coverage", func() {

src/java/finalize/finalize.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package finalize
22

33
import (
44
"fmt"
5-
"github.com/cloudfoundry/java-buildpack/src/java/common"
65
"os"
76
"path/filepath"
7+
"strings"
8+
9+
"github.com/cloudfoundry/java-buildpack/src/java/common"
810

911
"github.com/cloudfoundry/java-buildpack/src/java/containers"
1012
"github.com/cloudfoundry/java-buildpack/src/java/frameworks"
@@ -157,7 +159,7 @@ func (f *Finalizer) finalizeFrameworks() error {
157159
return nil
158160
}
159161

160-
f.Log.Info("Finalizing frameworks: %v", frameworkNames)
162+
f.Log.Info("Finalizing frameworks: %v", strings.Join(frameworkNames, ","))
161163

162164
// Finalize all detected frameworks
163165
for i, framework := range detectedFrameworks {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package frameworks
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/cloudfoundry/java-buildpack/src/java/common"
9+
"github.com/cloudfoundry/libbuildpack"
10+
)
11+
12+
const cfMetricsExporterDependencyName = "cf-metrics-exporter"
13+
const cfMetricsExporterDirName = "cf_metrics_exporter"
14+
15+
// Installer interface for dependency installation
16+
// Allows for mocking in tests
17+
// Only the InstallDependency method is needed for this framework
18+
// (matches the signature of libbuildpack.Installer)
19+
type Installer interface {
20+
InstallDependency(dep libbuildpack.Dependency, outputDir string) error
21+
}
22+
23+
type CfMetricsExporterFramework struct {
24+
context *common.Context
25+
installer Installer
26+
}
27+
28+
func NewCfMetricsExporterFramework(ctx *common.Context) *CfMetricsExporterFramework {
29+
installer := ctx.Installer
30+
if installer == nil {
31+
installer = libbuildpack.NewInstaller(ctx.Manifest)
32+
}
33+
return &CfMetricsExporterFramework{context: ctx, installer: installer}
34+
}
35+
36+
func (f *CfMetricsExporterFramework) Detect() (string, error) {
37+
enabled := os.Getenv("CF_METRICS_EXPORTER_ENABLED")
38+
if enabled == "true" || enabled == "TRUE" {
39+
_, err := f.context.Manifest.DefaultVersion(cfMetricsExporterDependencyName)
40+
if err != nil {
41+
return "", fmt.Errorf("cf-metrics-exporter version not found in manifest: %w", err)
42+
}
43+
return "CF Metrics Exporter", nil
44+
}
45+
return "", nil
46+
}
47+
48+
func (f *CfMetricsExporterFramework) getManifestDependency() (libbuildpack.Dependency, *libbuildpack.ManifestEntry, error) {
49+
dep, err := f.context.Manifest.DefaultVersion(cfMetricsExporterDependencyName)
50+
if err != nil {
51+
return libbuildpack.Dependency{}, nil, fmt.Errorf("cf-metrics-exporter version not found in manifest: %w", err)
52+
}
53+
entry, err := f.context.Manifest.GetEntry(dep)
54+
if err != nil {
55+
return dep, nil, fmt.Errorf("cf-metrics-exporter manifest entry not found: %w", err)
56+
}
57+
return dep, entry, nil
58+
}
59+
60+
func (f *CfMetricsExporterFramework) Supply() error {
61+
enabled := os.Getenv("CF_METRICS_EXPORTER_ENABLED")
62+
if enabled != "true" && enabled != "TRUE" {
63+
return nil
64+
}
65+
66+
dep, _, err := f.getManifestDependency()
67+
if err != nil {
68+
return err
69+
}
70+
71+
agentDir := filepath.Join(f.context.Stager.DepDir(), cfMetricsExporterDirName)
72+
jarName := fmt.Sprintf("cf-metrics-exporter-%s.jar", dep.Version)
73+
jarPath := filepath.Join(agentDir, jarName)
74+
75+
// Ensure agent directory exists
76+
if err := os.MkdirAll(agentDir, 0755); err != nil {
77+
return fmt.Errorf("failed to create agent dir: %w", err)
78+
}
79+
80+
// Download the JAR if not present
81+
if _, err := os.Stat(jarPath); os.IsNotExist(err) {
82+
if err := f.installer.InstallDependency(dep, agentDir); err != nil {
83+
return fmt.Errorf("failed to download cf-metrics-exporter: %w", err)
84+
}
85+
if _, err := os.Stat(jarPath); err != nil {
86+
return fmt.Errorf("expected jar file not found after download: %w", err)
87+
}
88+
}
89+
90+
// Log activation, including properties if set
91+
props := os.Getenv("CF_METRICS_EXPORTER_PROPS")
92+
if props != "" {
93+
f.context.Log.Info("CF Metrics Exporter v%s enabled, with properties: %s", dep.Version, props)
94+
} else {
95+
f.context.Log.Info("CF Metrics Exporter v%s enabled", dep.Version)
96+
}
97+
98+
return nil
99+
}
100+
101+
func (f *CfMetricsExporterFramework) Finalize() error {
102+
enabled := os.Getenv("CF_METRICS_EXPORTER_ENABLED")
103+
if enabled != "true" && enabled != "TRUE" {
104+
return nil
105+
}
106+
107+
dep, _, err := f.getManifestDependency()
108+
if err != nil {
109+
return err
110+
}
111+
112+
jarName := fmt.Sprintf("cf-metrics-exporter-%s.jar", dep.Version)
113+
depsIdx := f.context.Stager.DepsIdx()
114+
agentPath := fmt.Sprintf("$DEPS_DIR/%s/cf_metrics_exporter/%s", depsIdx, jarName)
115+
116+
props := os.Getenv("CF_METRICS_EXPORTER_PROPS")
117+
var javaOpt string
118+
if props != "" {
119+
javaOpt = fmt.Sprintf("-javaagent:%s=%s", agentPath, props)
120+
} else {
121+
javaOpt = fmt.Sprintf("-javaagent:%s", agentPath)
122+
}
123+
124+
// Priority 43: after SkyWalking (41), Splunk OTEL (42)
125+
return writeJavaOptsFile(f.context, 43, cfMetricsExporterDirName, javaOpt)
126+
}

0 commit comments

Comments
 (0)