Skip to content

Commit

Permalink
Merge pull request #120 from paketo-buildpacks/dotnet-agent
Browse files Browse the repository at this point in the history
Adds support for .NET agent with .Net Core apps
  • Loading branch information
dmikusa authored Jun 22, 2023
2 parents 50274d9 + 9012bf9 commit 5d4265f
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .github/pipeline-descriptor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ dependencies:
uses: docker://ghcr.io/paketo-buildpacks/actions/new-relic-dependency:main
with:
type: php
- id: new-relic-dotnet
uses: docker://ghcr.io/paketo-buildpacks/actions/new-relic-dependency:main
with:
type: dotnet
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ The buildpack will do the following for Python applications:
* You may override this file by including a `newrelic.ini` file at the root of your application.
* You will need to install the New Relic Python Agent, you can do this by adding New Relic as requirement in `requirements.txt` at the root of your application.

The buildpack will do the following for Dotnet (.NET) Core applications:

* Contributes a Dotnet agent to a layer and configures `$CORECLR_NEWRELIC_HOME` & `$CORECLR_PROFILER_PATH` to use it
* You can override the default global `newrelic.config` file with an app-local file containing custom configuration. See the new-relic [docs](https://docs.newrelic.com/docs/apm/agents/net-agent/configuration/net-agent-configuration/#config-options-precedence) for config precedence information.
* Transforms the contents of the binding secret to environment variables with the pattern `NEW_RELIC_<KEY>=<VALUE>`


## Configuration
| Environment Variable | Description
| -------------------- | -----------
Expand Down
16 changes: 15 additions & 1 deletion buildpack.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ api = "0.7"
description = "A Cloud Native Buildpack that contributes the New Relic Agent and configures it to connect to the service"
homepage = "https://github.com/paketo-buildpacks/new-relic"
id = "paketo-buildpacks/new-relic"
keywords = ["new-relic", "agent", "apm", "java", "node.js", "php", "python"]
keywords = ["new-relic", "agent", "apm", "java", "node.js", "php", "python", "dotnet"]
name = "Paketo Buildpack for New Relic"
sbom-formats = ["application/vnd.syft+json", "application/vnd.cyclonedx+json"]
version = "{{.version}}"
Expand Down Expand Up @@ -91,6 +91,20 @@ api = "0.7"
[[metadata.dependencies.licenses]]
uri = "https://docs.newrelic.com/docs/licenses/license-information"

[[metadata.dependencies]]
cpes = ["cpe:2.3:a:appdynamics:dotnet-agent:10.11.0:*:*:*:*:*:*:*"]
id = "new-relic-dotnet"
name = "New Relic Dotnet Agent"
purl = "pkg:generic/[email protected]?arch=amd64"
sha256 = "8a00aac013ea551b2f34e85dba2f9d5b097caf75e236eac45e26fbcb6d929d43"
stacks = ["io.buildpacks.stacks.bionic", "io.paketo.stacks.tiny", "*"]
uri = "https://download.newrelic.com/dot_net_agent/latest_release/newrelic-dotnet-agent_10.11.0_amd64.tar.gz"
version = "10.11.0"

[[metadata.dependencies.licenses]]
uri = "https://docs.newrelic.com/docs/licenses/license-information"


[[stacks]]
id = "io.buildpacks.stacks.bionic"

Expand Down
14 changes: 14 additions & 0 deletions newrelic/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,20 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
result.BOM.Entries = append(result.BOM.Entries, be)
}

if _, ok, err := pr.Resolve("new-relic-dotnet"); err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to resolve new-relic-dotnet plan entry\n%w", err)
} else if ok {
dep, err := dr.Resolve("new-relic-dotnet", "")
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err)
}

da, be := NewDotnetAgent(dep, dc)
da.Logger = b.Logger
result.Layers = append(result.Layers, da)
result.BOM.Entries = append(result.BOM.Entries, be)
}

h, be := libpak.NewHelperLayer(context.Buildpack, "properties")
h.Logger = b.Logger
result.Layers = append(result.Layers, h)
Expand Down
23 changes: 23 additions & 0 deletions newrelic/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,27 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(result.Layers[1].Name()).To(Equal("helper"))
Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{"properties"}))
})

it("contributes Dotnet agent API >= 0.7", func() {
ctx.Plan.Entries = append(ctx.Plan.Entries, libcnb.BuildpackPlanEntry{Name: "new-relic-dotnet"})
ctx.Buildpack.Metadata = map[string]interface{}{
"dependencies": []map[string]interface{}{
{
"id": "new-relic-dotnet",
"version": "1.1.1",
"stacks": []interface{}{"test-stack-id"},
},
},
}
ctx.Buildpack.API = "0.7"
ctx.StackID = "test-stack-id"

result, err := newrelic.Build{}.Build(ctx)
Expect(err).NotTo(HaveOccurred())

Expect(result.Layers).To(HaveLen(2))
Expect(result.Layers[0].Name()).To(Equal("new-relic-dotnet"))
Expect(result.Layers[1].Name()).To(Equal("helper"))
Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{"properties"}))
})
}
9 changes: 9 additions & 0 deletions newrelic/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error
{Name: "php"},
},
},
{
Provides: []libcnb.BuildPlanProvide{
{Name: "new-relic-dotnet"},
},
Requires: []libcnb.BuildPlanRequire{
{Name: "new-relic-dotnet"},
{Name: "dotnet-core-aspnet-runtime"},
},
},
{
Provides: []libcnb.BuildPlanProvide{
{Name: "new-relic-python-config"},
Expand Down
9 changes: 9 additions & 0 deletions newrelic/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
{Name: "php"},
},
},
{
Provides: []libcnb.BuildPlanProvide{
{Name: "new-relic-dotnet"},
},
Requires: []libcnb.BuildPlanRequire{
{Name: "new-relic-dotnet"},
{Name: "dotnet-core-aspnet-runtime"},
},
},
{
Provides: []libcnb.BuildPlanProvide{
{Name: "new-relic-python-config"},
Expand Down
70 changes: 70 additions & 0 deletions newrelic/dotnet_agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2018-2022 the original author or authors.
*
* 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
*
* https://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.
*/

package newrelic

import (
"fmt"
"os"
"path/filepath"

"github.com/buildpacks/libcnb"
"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/libpak/bard"
"github.com/paketo-buildpacks/libpak/crush"
)

type DotnetAgent struct {
LayerContributor libpak.DependencyLayerContributor
Logger bard.Logger
}

func NewDotnetAgent(dependency libpak.BuildpackDependency, cache libpak.DependencyCache) (DotnetAgent, libcnb.BOMEntry) {
contributor, entry := libpak.NewDependencyLayer(dependency, cache, libcnb.LayerTypes{
Launch: true,
})
return DotnetAgent{
LayerContributor: contributor,
}, entry
}

func (d DotnetAgent) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
d.LayerContributor.Logger = d.Logger

layer, err := d.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
d.Logger.Bodyf("Expanding to %s", layer.Path)

if err := crush.ExtractTarGz(artifact, layer.Path, 1); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to expand New Relic\n%w", err)
}

layer.LaunchEnvironment.Default("CORECLR_NEWRELIC_HOME", layer.Path)
layer.LaunchEnvironment.Default("CORECLR_ENABLE_PROFILING", "1")
layer.LaunchEnvironment.Default("CORECLR_PROFILER", "{36032161-FFC0-4B61-B559-F6C5D41BAE5A}")
layer.LaunchEnvironment.Default("CORECLR_PROFILER_PATH", filepath.Join(layer.Path, "libNewRelicProfiler.so"))

return layer, nil
})
if err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to contribute dotnet agent\n%w", err)
}

return layer, nil
}

func (n DotnetAgent) Name() string {
return n.LayerContributor.LayerName()
}
73 changes: 73 additions & 0 deletions newrelic/dotnet_agent_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2018-2022 the original author or authors.
*
* 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
*
* https://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.
*/

package newrelic_test

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/buildpacks/libcnb"
. "github.com/onsi/gomega"
"github.com/paketo-buildpacks/libpak"
"github.com/sclevine/spec"

"github.com/paketo-buildpacks/new-relic/v4/newrelic"
)

func testDotnetAgent(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect

ctx libcnb.BuildContext
)

it.Before(func() {
var err error

ctx.Layers.Path, err = ioutil.TempDir("", "dotnet-agent-layers")
Expect(err).NotTo(HaveOccurred())
})

it.After(func() {
Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed())
})

it("contributes Dotnet agent", func() {
dep := libpak.BuildpackDependency{
URI: "https://localhost/stub-new-relic-agent.tar.gz",
SHA256: "52d21d0eac639a8b5d47f52b0256a5acb94983282ecfa8f7a11bbaf85da77425",
}
dc := libpak.DependencyCache{CachePath: "testdata"}

d, _ := newrelic.NewDotnetAgent(dep, dc)
layer, err := ctx.Layers.Layer("test-layer")
Expect(err).NotTo(HaveOccurred())

layer, err = d.Contribute(layer)
Expect(err).NotTo(HaveOccurred())

Expect(layer.Launch).To(BeTrue())
Expect(filepath.Join(layer.Path, "fixture-marker")).To(BeARegularFile())

Expect(layer.LaunchEnvironment["CORECLR_ENABLE_PROFILING.default"]).To(Equal("1"))
Expect(layer.LaunchEnvironment["CORECLR_NEWRELIC_HOME.default"]).To(Equal(filepath.Join(layer.Path)))
Expect(layer.LaunchEnvironment["CORECLR_PROFILER.default"]).To(Equal("{36032161-FFC0-4B61-B559-F6C5D41BAE5A}"))
Expect(layer.LaunchEnvironment["CORECLR_PROFILER_PATH.default"]).To(Equal(filepath.Join(layer.Path, "libNewRelicProfiler.so")))
})
}

0 comments on commit 5d4265f

Please sign in to comment.